what_dya_return 0.1.0

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