what_dya_return 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 0f7dfddc7b09e314b0c96b8e8ad634f517f03e5252722c8aa16f3883caba8293
4
+ data.tar.gz: 3f6ec35cc912717543a3811c38bfbce34a92b9256ea753d02a0580e06cb252d8
5
+ SHA512:
6
+ metadata.gz: 11effd57e51749aac58251616c68d3494eab40bff6d0df63b4c8f29e182ff17fa4e35210f8c57b298b9efe68699f7822acb4c3a3af761a4f66bd7835e641aad0
7
+ data.tar.gz: 1718c98ba23d4801ba67449843e2742d3f4449a867fa00518c683328765f404263704ef4a3389685c0d55ef782fa83b4a0210a0c7936b7e4991b4fff709bd2fe
data/.rubocop.yml ADDED
@@ -0,0 +1,10 @@
1
+ require:
2
+ - rubocop-performance
3
+
4
+ Metrics/ClassLength:
5
+ Exclude:
6
+ - 'test/what_dya_return/extractor_test.rb'
7
+
8
+ Metrics/MethodLength:
9
+ Exclude:
10
+ - 'test/what_dya_return/extractor_test.rb'
data/.ruby-version ADDED
@@ -0,0 +1 @@
1
+ 3.0.6
data/Gemfile ADDED
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in what_dya_return.gemspec
6
+ gemspec
7
+
8
+ group :development, :test do
9
+ gem 'rake', '~> 13.0'
10
+ gem 'rubocop'
11
+ gem 'rubocop-performance'
12
+ gem 'test-unit', '~> 3.0'
13
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2023 Wataru MIYAGUNI
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,47 @@
1
+ # WhatDyaReturn
2
+
3
+ :angel: "What do you return?"
4
+
5
+ ```rb
6
+ def foo
7
+ if bar
8
+ 42
9
+ else
10
+ 'baz'
11
+ end
12
+ end
13
+ ```
14
+
15
+ :robot: "42 and 'baz'"
16
+
17
+ ## Installation
18
+
19
+ $ gem install what_dya_return
20
+
21
+ ## Usage
22
+
23
+ ```rb
24
+ result = WhatDyaReturn::Extractor.new.extract(<<-CODE)
25
+ def foo
26
+ if bar
27
+ 42
28
+ else
29
+ 'baz'
30
+ end
31
+ end
32
+ CODE
33
+
34
+ result # => ['42', "'baz'"]
35
+ ```
36
+
37
+ ## Caution
38
+
39
+ `what_dya_return` is an experimental project. There are still many statements that are not supported.
40
+
41
+ ## Contributing
42
+
43
+ Bug reports and pull requests are welcome on GitHub at https://github.com/gongo/what_dya_return .
44
+
45
+ ## License
46
+
47
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rake/testtask'
5
+
6
+ Rake::TestTask.new(:test) do |t|
7
+ t.libs << 'test'
8
+ t.libs << 'lib'
9
+ t.test_files = FileList['test/**/*_test.rb']
10
+ end
11
+
12
+ task default: %i[test]
@@ -0,0 +1,6 @@
1
+ module WhatDyaReturn
2
+ module AST
3
+ class BeginNode < ::RuboCop::AST::Node
4
+ end
5
+ end
6
+ end
@@ -0,0 +1,23 @@
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
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module AST
5
+ class RuboCop::AST::IfNode
6
+ #
7
+ # @example
8
+ #
9
+ # if false # or `nil`
10
+ # 42 # unreachable
11
+ # else
12
+ # run
13
+ # end
14
+ #
15
+ # @return [Boolean]
16
+ #
17
+ def if_branch_reachable?
18
+ ((if? || ternary? || elsif?) && condition.falsey_literal?.!) || (unless? && condition.truthy_literal?.!)
19
+ end
20
+
21
+ #
22
+ # @example
23
+ #
24
+ # if true
25
+ # 42
26
+ # else
27
+ # run # unreachable
28
+ # end
29
+ #
30
+ # @return [Boolean]
31
+ #
32
+ def else_branch_reachable?
33
+ ((if? || ternary? || elsif?) && condition.truthy_literal?.!) || (unless? && condition.falsey_literal?.!)
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubocop-ast'
4
+ require 'unparser'
5
+ require_relative 'processor'
6
+
7
+ module WhatDyaReturn
8
+ class Extractor
9
+ #
10
+ # @param [String] source_code
11
+ # @return [Array<String>]
12
+ # @raise [WhatDyaReturn::SyntaxErrfor] if `source_code` cannot be parsed
13
+ #
14
+ def extract(source_code)
15
+ processed = ::RuboCop::AST::ProcessedSource.new(source_code, RUBY_VERSION.to_f)
16
+ raise WhatDyaReturn::SyntaxError unless processed.valid_syntax?
17
+
18
+ Processor.new.process(processed.ast).map do |node|
19
+ node.nil? ? 'nil' : Unparser.unparse(node)
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,136 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'ext/rubocop/ast/builder'
4
+ require_relative 'ext/rubocop/ast/node/if_node'
5
+ require_relative 'statement_checker'
6
+
7
+ module WhatDyaReturn
8
+ class Processor
9
+ #
10
+ # @param [RuboCop::AST::DefNode] node
11
+ # @return [Array<RuboCop::AST::Node>]
12
+ #
13
+ def process(node)
14
+ @return_nodes = []
15
+
16
+ check_branch(node.body, true)
17
+
18
+ @return_nodes
19
+ end
20
+
21
+ private
22
+
23
+ #
24
+ # @param [RuboCop::AST::Node] node
25
+ # @param [Boolean] is_ret_expr Whether current scope is within return expression
26
+ # @return [void]
27
+ #
28
+ def check_branch(node, is_ret_expr)
29
+ unless node
30
+ @return_nodes << node if is_ret_expr
31
+ return
32
+ end
33
+
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)
47
+ else
48
+ @return_nodes << node if is_ret_expr
49
+ end
50
+ end
51
+
52
+ #
53
+ # @param [WhatDyaReturn::AST::BeginNode] node
54
+ # @return [void]
55
+ #
56
+ def check_begin_node(node, is_ret_expr)
57
+ 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
60
+ end
61
+
62
+ check_branch(node.children[-1], is_ret_expr)
63
+ end
64
+
65
+ #
66
+ # @param [RuboCop::AST::ReturnNode] node
67
+ # @return [void]
68
+ #
69
+ def check_return_node(node)
70
+ check_branch(node.children.first, true)
71
+ end
72
+
73
+ #
74
+ # @param [RuboCop::AST::IfNode] node
75
+ # @param [Boolean] is_ret_expr Whether current scope is within return expression
76
+ # @return [void]
77
+ #
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)
81
+ end
82
+
83
+ #
84
+ # @param [RuboCop::AST::CaseNode] node
85
+ # @param [Boolean] is_ret_expr Whether current scope is within return expression
86
+ # @return [void]
87
+ #
88
+ def check_case_node(node, is_ret_expr)
89
+ node.when_branches.each do |when_branch|
90
+ #
91
+ # case # no condition
92
+ # when false
93
+ # 1 # unreachable
94
+ # when 'qux'
95
+ # 2 # reachable
96
+ # end
97
+ #
98
+ next if node.condition.nil? && when_branch.conditions.all?(&:falsey_literal?)
99
+
100
+ check_branch(when_branch.body, is_ret_expr)
101
+ end
102
+
103
+ check_branch(node.else_branch, is_ret_expr)
104
+ end
105
+
106
+ #
107
+ # @param [RuboCop::AST::RescueNode] node
108
+ # @param [Boolean] is_ret_expr Whether current scope is within return expression
109
+ # @return [void]
110
+ #
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)
114
+ else
115
+ check_branch(node.body, is_ret_expr)
116
+ end
117
+
118
+ node.resbody_branches.each do |resbody_branch|
119
+ check_branch(resbody_branch.body, is_ret_expr)
120
+ end
121
+ end
122
+
123
+ #
124
+ # @param [RuboCop::AST::EnsureNode] node
125
+ # @param [Boolean] is_ret_expr Whether current scope is within return expression
126
+ # @return [void]
127
+ #
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
131
+ else
132
+ check_branch(node.body, false)
133
+ end
134
+ end
135
+ end
136
+ end
@@ -0,0 +1,159 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This file is based on https://github.com/rubocop/rubocop/blob/v1.50.2/lib/rubocop/cop/lint/unreachable_code.rb
4
+ # The original code is licensed under the MIT License.
5
+ #
6
+ # https://github.com/rubocop/rubocop/blob/v1.50.2/LICENSE.txt
7
+
8
+ module WhatDyaReturn
9
+ module StatementChecker
10
+ class ReachableToNextStatement
11
+ #
12
+ # @param node [::RuboCop::AST::Node]
13
+ # @return [Boolean]
14
+ #
15
+ def ok?(node)
16
+ return false if flow_terminate_command?(node)
17
+
18
+ case node
19
+ when WhatDyaReturn::AST::BeginNode
20
+ check_begin(node)
21
+ when ::RuboCop::AST::IfNode
22
+ check_if(node)
23
+ when ::RuboCop::AST::CaseNode
24
+ check_case(node)
25
+ else
26
+ true
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ #
33
+ # @example Return true
34
+ #
35
+ # def foo
36
+ # ( # begin start
37
+ # "a"
38
+ # "b"
39
+ # ) # begin end
40
+ #
41
+ # "c" # reachable
42
+ # end
43
+ #
44
+ # @example Return false
45
+ #
46
+ # def foo
47
+ # ( # begin start
48
+ # "a"
49
+ # return "b"
50
+ # ) # begin end
51
+ #
52
+ # "c" # unreachable
53
+ # end
54
+ #
55
+ # @param node [WhatDyaReturn::AST::BeginNode]
56
+ # @return [Boolean]
57
+ #
58
+ def check_begin(node)
59
+ node.children.all? { |child| ok?(child) }
60
+ end
61
+
62
+ #
63
+ # @example Return true
64
+ #
65
+ # if foo?
66
+ # return "a"
67
+ # end
68
+ #
69
+ # "b" # reachable
70
+ #
71
+ # @example Return true with else statement
72
+ #
73
+ # if foo?
74
+ # return "a"
75
+ # else
76
+ # "c"
77
+ # end
78
+ #
79
+ # "b" # reachable
80
+ #
81
+ # @example Return false
82
+ #
83
+ # if foo?
84
+ # return "a"
85
+ # else
86
+ # return "c"
87
+ # end
88
+ #
89
+ # "b" # unreachable
90
+ #
91
+ # @param node [::RuboCop::AST::IfNode]
92
+ # @return [Boolean]
93
+ #
94
+ def check_if(node)
95
+ return true if node.else_branch.nil?
96
+
97
+ ok?(node.body) || ok?(node.else_branch)
98
+ end
99
+
100
+ #
101
+ # @example Return true without else statement
102
+ #
103
+ # case foo
104
+ # when "bar"
105
+ # return :bar
106
+ # end
107
+ #
108
+ # "b" # reachable
109
+ #
110
+ # @example Return true with else statement
111
+ #
112
+ # case foo
113
+ # when "bar"
114
+ # return :bar
115
+ # else
116
+ # :baz
117
+ # end
118
+ #
119
+ # "b" # reachable
120
+ #
121
+ # @example Return false
122
+ #
123
+ # case foo
124
+ # when "bar"
125
+ # return :bar
126
+ # else
127
+ # return :baz
128
+ # end
129
+ #
130
+ # "b" # unreachable
131
+ #
132
+ # @param node [::RuboCop::AST::CaseNode]
133
+ # @return [Boolean]
134
+ #
135
+ def check_case(node)
136
+ return true if node.else_branch.nil?
137
+ return true if ok?(node.else_branch)
138
+
139
+ node.when_branches.any? { |when_branch| ok?(when_branch.body) }
140
+ 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
+ end
158
+ end
159
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'statement_checker/reachable_to_next_statement'
4
+
5
+ module WhatDyaReturn
6
+ module StatementChecker
7
+ #
8
+ # @param node [::RuboCop::AST::Node]
9
+ # @return [Boolean]
10
+ #
11
+ def self.reachable_to_next_statement?(node)
12
+ @reachable_to_next_statement ||= ReachableToNextStatement.new
13
+ @reachable_to_next_statement.ok?(node)
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module WhatDyaReturn
4
+ VERSION = '0.1.0'
5
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'what_dya_return/extractor'
4
+ require_relative 'what_dya_return/version'
5
+
6
+ module WhatDyaReturn
7
+ class Error < StandardError; end
8
+ class SyntaxError < Error; end
9
+ end
@@ -0,0 +1,4 @@
1
+ module WhatDyaReturn
2
+ VERSION: String
3
+ # See the writing guide of rbs: https://github.com/ruby/rbs#guides
4
+ end
metadata ADDED
@@ -0,0 +1,90 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: what_dya_return
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Wataru MIYAGUNI
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2023-07-19 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: rubocop-ast
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '1'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '1'
27
+ - !ruby/object:Gem::Dependency
28
+ name: unparser
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description:
42
+ email:
43
+ - gonngo@gmail.com
44
+ executables: []
45
+ extensions: []
46
+ extra_rdoc_files: []
47
+ files:
48
+ - ".rubocop.yml"
49
+ - ".ruby-version"
50
+ - Gemfile
51
+ - LICENSE.txt
52
+ - README.md
53
+ - Rakefile
54
+ - lib/what_dya_return.rb
55
+ - 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
58
+ - lib/what_dya_return/extractor.rb
59
+ - lib/what_dya_return/processor.rb
60
+ - lib/what_dya_return/statement_checker.rb
61
+ - lib/what_dya_return/statement_checker/reachable_to_next_statement.rb
62
+ - lib/what_dya_return/version.rb
63
+ - sig/what_dya_return.rbs
64
+ homepage: https://github.com/gongo/what_dya_return
65
+ licenses:
66
+ - MIT
67
+ metadata:
68
+ homepage_uri: https://github.com/gongo/what_dya_return
69
+ source_code_uri: https://github.com/gongo/what_dya_return
70
+ changelog_uri: https://github.com/gongo/what_dya_return/blob/main/CHANGELOG.md
71
+ post_install_message:
72
+ rdoc_options: []
73
+ require_paths:
74
+ - lib
75
+ required_ruby_version: !ruby/object:Gem::Requirement
76
+ requirements:
77
+ - - ">="
78
+ - !ruby/object:Gem::Version
79
+ version: 3.0.0
80
+ required_rubygems_version: !ruby/object:Gem::Requirement
81
+ requirements:
82
+ - - ">="
83
+ - !ruby/object:Gem::Version
84
+ version: '0'
85
+ requirements: []
86
+ rubygems_version: 3.2.33
87
+ signing_key:
88
+ specification_version: 4
89
+ summary: Predict Ruby code return values.
90
+ test_files: []