private_detective 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 +7 -0
- data/.idea/.gitignore +8 -0
- data/.idea/misc.xml +4 -0
- data/.idea/modules.xml +8 -0
- data/.idea/private_detective.iml +43 -0
- data/.idea/vcs.xml +6 -0
- data/.rspec +3 -0
- data/.rubocop.yml +13 -0
- data/CHANGELOG.md +5 -0
- data/Gemfile +13 -0
- data/Gemfile.lock +75 -0
- data/LICENSE.txt +21 -0
- data/README.md +73 -0
- data/Rakefile +12 -0
- data/bin/private_detective +19 -0
- data/lib/private_detective/analyze_file.rb +32 -0
- data/lib/private_detective/analyze_node.rb +130 -0
- data/lib/private_detective/analyze_project.rb +49 -0
- data/lib/private_detective/report.rb +57 -0
- data/lib/private_detective/version.rb +5 -0
- data/lib/private_detective.rb +17 -0
- data/private_detective.gemspec +39 -0
- data/sig/private_detective.rbs +4 -0
- metadata +84 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 3b2a4fd2e7baae3f735e10f71e48d5b9536d54831244e36283afaacc028919c5
|
|
4
|
+
data.tar.gz: 104bfcd621e4fb437c5956f6ac9793ab7116ef8a26ae2927f78deec9ec668d80
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 60038d8884c2bec910a34ebc060265beb5c1068d91b5ae23fc4365dabd0ec766e06e10239021dcb107bd60a7836fe4eaa5cdd33acbd6421b5a28f7258a919859
|
|
7
|
+
data.tar.gz: 6ee76e06401e130ebde370d1389ff714cabc9a20707bcdb8385d86e73134eea2d7d8159d9e39b0c59371db2a30d6c3b666ad68f13d55a89a78e38490c28064c6
|
data/.idea/.gitignore
ADDED
data/.idea/misc.xml
ADDED
data/.idea/modules.xml
ADDED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<project version="4">
|
|
3
|
+
<component name="ProjectModuleManager">
|
|
4
|
+
<modules>
|
|
5
|
+
<module fileurl="file://$PROJECT_DIR$/.idea/private_detective.iml" filepath="$PROJECT_DIR$/.idea/private_detective.iml" />
|
|
6
|
+
</modules>
|
|
7
|
+
</component>
|
|
8
|
+
</project>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
2
|
+
<module type="RUBY_MODULE" version="4">
|
|
3
|
+
<component name="ModuleRunConfigurationManager">
|
|
4
|
+
<shared />
|
|
5
|
+
</component>
|
|
6
|
+
<component name="NewModuleRootManager">
|
|
7
|
+
<content url="file://$MODULE_DIR$">
|
|
8
|
+
<sourceFolder url="file://$MODULE_DIR$/features" isTestSource="true" />
|
|
9
|
+
<sourceFolder url="file://$MODULE_DIR$/spec" isTestSource="true" />
|
|
10
|
+
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
|
11
|
+
</content>
|
|
12
|
+
<orderEntry type="inheritedJdk" />
|
|
13
|
+
<orderEntry type="sourceFolder" forTests="false" />
|
|
14
|
+
<orderEntry type="library" scope="PROVIDED" name="ast (v2.4.2, rbenv: 2.7.3) [gem]" level="application" />
|
|
15
|
+
<orderEntry type="library" scope="PROVIDED" name="byebug (v11.1.3, rbenv: 2.7.3) [gem]" level="application" />
|
|
16
|
+
<orderEntry type="library" scope="PROVIDED" name="coderay (v1.1.3, rbenv: 2.7.3) [gem]" level="application" />
|
|
17
|
+
<orderEntry type="library" scope="PROVIDED" name="colorize (v0.8.1, rbenv: 2.7.3) [gem]" level="application" />
|
|
18
|
+
<orderEntry type="library" scope="PROVIDED" name="diff-lcs (v1.5.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
19
|
+
<orderEntry type="library" scope="PROVIDED" name="language_server-protocol (v3.17.0.3, rbenv: 2.7.3) [gem]" level="application" />
|
|
20
|
+
<orderEntry type="library" scope="PROVIDED" name="method_source (v1.0.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
21
|
+
<orderEntry type="library" scope="PROVIDED" name="parallel (v1.23.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
22
|
+
<orderEntry type="library" scope="PROVIDED" name="parser (v3.2.2.4, rbenv: 2.7.3) [gem]" level="application" />
|
|
23
|
+
<orderEntry type="library" scope="PROVIDED" name="pry (v0.14.2, rbenv: 2.7.3) [gem]" level="application" />
|
|
24
|
+
<orderEntry type="library" scope="PROVIDED" name="pry-byebug (v3.10.1, rbenv: 2.7.3) [gem]" level="application" />
|
|
25
|
+
<orderEntry type="library" scope="PROVIDED" name="racc (v1.7.3, rbenv: 2.7.3) [gem]" level="application" />
|
|
26
|
+
<orderEntry type="library" scope="PROVIDED" name="rainbow (v3.1.1, rbenv: 2.7.3) [gem]" level="application" />
|
|
27
|
+
<orderEntry type="library" scope="PROVIDED" name="rake (v13.1.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
28
|
+
<orderEntry type="library" scope="PROVIDED" name="rexml (v3.2.6, rbenv: 2.7.3) [gem]" level="application" />
|
|
29
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec (v3.12.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
30
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-core (v3.12.2, rbenv: 2.7.3) [gem]" level="application" />
|
|
31
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-expectations (v3.12.3, rbenv: 2.7.3) [gem]" level="application" />
|
|
32
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-mocks (v3.12.6, rbenv: 2.7.3) [gem]" level="application" />
|
|
33
|
+
<orderEntry type="library" scope="PROVIDED" name="rspec-support (v3.12.1, rbenv: 2.7.3) [gem]" level="application" />
|
|
34
|
+
<orderEntry type="library" scope="PROVIDED" name="rubocop-ast (v1.30.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
35
|
+
<orderEntry type="library" scope="PROVIDED" name="ruby-progressbar (v1.13.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
36
|
+
<orderEntry type="library" scope="PROVIDED" name="unicode-display_width (v2.5.0, rbenv: 2.7.3) [gem]" level="application" />
|
|
37
|
+
</component>
|
|
38
|
+
<component name="RakeTasksCache">
|
|
39
|
+
<option name="myRootTask">
|
|
40
|
+
<RakeTaskImpl id="rake" />
|
|
41
|
+
</option>
|
|
42
|
+
</component>
|
|
43
|
+
</module>
|
data/.idea/vcs.xml
ADDED
data/.rspec
ADDED
data/.rubocop.yml
ADDED
data/CHANGELOG.md
ADDED
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 private_detective.gemspec
|
|
6
|
+
gemspec
|
|
7
|
+
|
|
8
|
+
gem "colorize"
|
|
9
|
+
gem "parser"
|
|
10
|
+
gem "pry-byebug"
|
|
11
|
+
gem "rake", "~> 13.0"
|
|
12
|
+
gem "rspec", "~> 3.0"
|
|
13
|
+
gem "rubocop", "~> 1.21"
|
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
private_detective (0.1.0)
|
|
5
|
+
colorize (~> 0.8.1)
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
ast (2.4.2)
|
|
11
|
+
byebug (11.1.3)
|
|
12
|
+
coderay (1.1.3)
|
|
13
|
+
colorize (0.8.1)
|
|
14
|
+
diff-lcs (1.5.0)
|
|
15
|
+
json (2.7.1)
|
|
16
|
+
language_server-protocol (3.17.0.3)
|
|
17
|
+
method_source (1.0.0)
|
|
18
|
+
parallel (1.23.0)
|
|
19
|
+
parser (3.2.2.4)
|
|
20
|
+
ast (~> 2.4.1)
|
|
21
|
+
racc
|
|
22
|
+
pry (0.14.2)
|
|
23
|
+
coderay (~> 1.1)
|
|
24
|
+
method_source (~> 1.0)
|
|
25
|
+
pry-byebug (3.10.1)
|
|
26
|
+
byebug (~> 11.0)
|
|
27
|
+
pry (>= 0.13, < 0.15)
|
|
28
|
+
racc (1.7.3)
|
|
29
|
+
rainbow (3.1.1)
|
|
30
|
+
rake (13.1.0)
|
|
31
|
+
regexp_parser (2.8.3)
|
|
32
|
+
rexml (3.2.6)
|
|
33
|
+
rspec (3.12.0)
|
|
34
|
+
rspec-core (~> 3.12.0)
|
|
35
|
+
rspec-expectations (~> 3.12.0)
|
|
36
|
+
rspec-mocks (~> 3.12.0)
|
|
37
|
+
rspec-core (3.12.2)
|
|
38
|
+
rspec-support (~> 3.12.0)
|
|
39
|
+
rspec-expectations (3.12.3)
|
|
40
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
41
|
+
rspec-support (~> 3.12.0)
|
|
42
|
+
rspec-mocks (3.12.6)
|
|
43
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
44
|
+
rspec-support (~> 3.12.0)
|
|
45
|
+
rspec-support (3.12.1)
|
|
46
|
+
rubocop (1.58.0)
|
|
47
|
+
json (~> 2.3)
|
|
48
|
+
language_server-protocol (>= 3.17.0)
|
|
49
|
+
parallel (~> 1.10)
|
|
50
|
+
parser (>= 3.2.2.4)
|
|
51
|
+
rainbow (>= 2.2.2, < 4.0)
|
|
52
|
+
regexp_parser (>= 1.8, < 3.0)
|
|
53
|
+
rexml (>= 3.2.5, < 4.0)
|
|
54
|
+
rubocop-ast (>= 1.30.0, < 2.0)
|
|
55
|
+
ruby-progressbar (~> 1.7)
|
|
56
|
+
unicode-display_width (>= 2.4.0, < 3.0)
|
|
57
|
+
rubocop-ast (1.30.0)
|
|
58
|
+
parser (>= 3.2.1.0)
|
|
59
|
+
ruby-progressbar (1.13.0)
|
|
60
|
+
unicode-display_width (2.5.0)
|
|
61
|
+
|
|
62
|
+
PLATFORMS
|
|
63
|
+
x86_64-darwin-23
|
|
64
|
+
|
|
65
|
+
DEPENDENCIES
|
|
66
|
+
colorize
|
|
67
|
+
parser
|
|
68
|
+
private_detective!
|
|
69
|
+
pry-byebug
|
|
70
|
+
rake (~> 13.0)
|
|
71
|
+
rspec (~> 3.0)
|
|
72
|
+
rubocop (~> 1.21)
|
|
73
|
+
|
|
74
|
+
BUNDLED WITH
|
|
75
|
+
2.4.10
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Ross Buddie
|
|
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,73 @@
|
|
|
1
|
+
# Private Detective 🕵️
|
|
2
|
+
## A Ruby gem to analyze method visibility in your Ruby project
|
|
3
|
+
|
|
4
|
+
## Overview
|
|
5
|
+
|
|
6
|
+
Private Detective was created to address a common issue in Ruby projects where determining the appropriate visibility for class methods is sometimes overlooked. Often, it is not immediately clear whether a method should be private, protected or public. Private Detective simplifies this process by providing tools to analyze Ruby source code files, helping developers identify and refine the visibility of their methods.
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
Add this line to your application's Gemfile:
|
|
11
|
+
|
|
12
|
+
```ruby
|
|
13
|
+
gem 'private_detective'
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
and then execute:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
$ private_detective
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Why did I build this gem?
|
|
23
|
+
|
|
24
|
+
In real-world scenarios, it is common for public methods to inadvertently expose internal details, or for private methods to be unnecessarily restrictive. This can lead to issues with maintainability, code readability, and overall project health. Private Detective was developed to streamline the identification and rectification of such visibility issues, offering a clearer understanding of method visibility within Ruby projects.
|
|
25
|
+
|
|
26
|
+
More importantly, it was also a good excuse to look at the inner workings of the Rubocop gem and build a gem of my own exploring the Ruby file parser and to traverse AST nodes.
|
|
27
|
+
|
|
28
|
+
## How It Works
|
|
29
|
+
|
|
30
|
+
The Private Detective gem employs the following process to analyze method visibility in your Ruby project:
|
|
31
|
+
|
|
32
|
+
1. **Iteration Over Project Files:**
|
|
33
|
+
- The gem iterates over your project files (currently app/models only), inspecting the Ruby source code.
|
|
34
|
+
|
|
35
|
+
2. **Parsing to AST Nodes:**
|
|
36
|
+
- Each file is parsed into an Abstract Syntax Tree (AST) node using the `parser` gem.
|
|
37
|
+
|
|
38
|
+
3. **Traversing the AST:**
|
|
39
|
+
- The gem traverses the AST nodes to identify class methods and their visibility modifiers: Private, Protected and the default Public.
|
|
40
|
+
|
|
41
|
+
4. **Visibility Analysis:**
|
|
42
|
+
- Based on the identified methods and modifiers, Private Detective provides insights into potential issues related to method visibility.
|
|
43
|
+
|
|
44
|
+
## Example response
|
|
45
|
+
|
|
46
|
+
```response
|
|
47
|
+
Private Detective found the following Class method information in your project:
|
|
48
|
+
|
|
49
|
+
Class: Client
|
|
50
|
+
Method: test_method_rubocop_client, visibility: public
|
|
51
|
+
Method contents:
|
|
52
|
+
models/user.rb:10:4 User#test_method_rubocop private [Correctable]
|
|
53
|
+
models/user.rb:11:4 User#test_private_method_rubocop protected [Correctable]
|
|
54
|
+
Method: test_additional_method_example, visibility: private
|
|
55
|
+
Method: test_additional_method_example_2, visibility: private
|
|
56
|
+
|
|
57
|
+
Class: Prompt
|
|
58
|
+
Method: message_content, visibility: public
|
|
59
|
+
|
|
60
|
+
Class: User
|
|
61
|
+
Method: test_method_rubocop, visibility: private
|
|
62
|
+
Method: test_private_method_rubocop, visibility: protected
|
|
63
|
+
|
|
64
|
+
End of report
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Contributing
|
|
68
|
+
|
|
69
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/rossme/private_detective.
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
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,19 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "parser/current"
|
|
6
|
+
require "colorize"
|
|
7
|
+
require "private_detective"
|
|
8
|
+
|
|
9
|
+
files = if ARGV.empty?
|
|
10
|
+
Dir.glob("#{Dir.pwd}/**/app/models/*.rb")
|
|
11
|
+
else
|
|
12
|
+
ARGV
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private_detective = PrivateDetective::AnalyzeProject.new(files: files)
|
|
16
|
+
private_detective&.analyze_project
|
|
17
|
+
|
|
18
|
+
require "irb"
|
|
19
|
+
IRB.start(__FILE__)
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PrivateDetective
|
|
4
|
+
class AnalyzeFile
|
|
5
|
+
|
|
6
|
+
attr_reader :file_path, :report, :node
|
|
7
|
+
|
|
8
|
+
# @param [String] file_path, [Hash] report
|
|
9
|
+
def initialize(file_path:, report:)
|
|
10
|
+
@file_path = file_path
|
|
11
|
+
@report = report
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# @return [Hash] report
|
|
15
|
+
def analyze_file
|
|
16
|
+
@node = Parser::CurrentRuby.parse(File.read(file_path))
|
|
17
|
+
return unless node.is_a?(Parser::AST::Node)
|
|
18
|
+
|
|
19
|
+
analyze_node
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
private
|
|
23
|
+
|
|
24
|
+
# @return [Hash] report
|
|
25
|
+
def analyze_node
|
|
26
|
+
analyze_node = PrivateDetective::AnalyzeNode.new(node: node, file_path: file_path)
|
|
27
|
+
analyze_node.process_node
|
|
28
|
+
|
|
29
|
+
report.merge!(analyze_node.report)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PrivateDetective
|
|
4
|
+
class AnalyzeNode
|
|
5
|
+
|
|
6
|
+
ACCESS_CONTROL_KEYWORDS = %i[public private protected].freeze
|
|
7
|
+
|
|
8
|
+
attr_reader :class_details, :super_class, :class_body, :file_path, :report
|
|
9
|
+
|
|
10
|
+
# @param [Parser::AST::Node] node, [String] file_path
|
|
11
|
+
def initialize(node:, file_path: nil)
|
|
12
|
+
@class_details, @super_class, @class_body = *node
|
|
13
|
+
@file_path = file_path
|
|
14
|
+
@report = { class_name => [] }
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# @return [Hash] report
|
|
18
|
+
def process_node
|
|
19
|
+
return unless class_pattern?
|
|
20
|
+
|
|
21
|
+
create_report
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
# @return [Hash] report
|
|
27
|
+
def create_report
|
|
28
|
+
class_body.children.each do |child_node|
|
|
29
|
+
next unless def_method?(child_node)
|
|
30
|
+
|
|
31
|
+
report[class_name] << build_method_hash(child_node)
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# @return [Boolean]
|
|
36
|
+
def class_pattern?
|
|
37
|
+
class_name && super_class&.children[1] == :ApplicationRecord && class_body&.type == :begin
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def def_method?(child_node)
|
|
42
|
+
child_node&.type == :def
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# @return [Hash] method name, class name, visibility, on_send, location
|
|
46
|
+
def build_method_hash(child_node)
|
|
47
|
+
{
|
|
48
|
+
file_path: shortened_file_path,
|
|
49
|
+
class: class_name,
|
|
50
|
+
method: child_node.children[0],
|
|
51
|
+
visibility: method_visibility(child_node),
|
|
52
|
+
on_send: build_send_hash(child_node),
|
|
53
|
+
location: {
|
|
54
|
+
location: child_node.location,
|
|
55
|
+
line: child_node.location.line,
|
|
56
|
+
column: child_node.location.column,
|
|
57
|
+
source: child_node.location.expression.source
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# @return [Hash] send_method_hash(send_node)
|
|
63
|
+
# @example { 0 => { method: :my_user_method, class: :user } }
|
|
64
|
+
def build_send_hash(child_node)
|
|
65
|
+
send_hash = {}
|
|
66
|
+
child_node.children[2].children.each_with_index do |send_node, index|
|
|
67
|
+
next unless valid_send_node?(send_node)
|
|
68
|
+
|
|
69
|
+
send_hash.merge!(index => send_method_hash(send_node))
|
|
70
|
+
end
|
|
71
|
+
send_hash
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def valid_send_node?(send_node)
|
|
76
|
+
return false unless send_node.is_a?(Parser::AST::Node)
|
|
77
|
+
|
|
78
|
+
send_node.type == :send && send_node.children&.first&.type == :const
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# @return [Hash] method name and class name
|
|
82
|
+
def send_method_hash(send_node)
|
|
83
|
+
{
|
|
84
|
+
method: send_node.children[1],
|
|
85
|
+
class: send_node.children[0].children[1],
|
|
86
|
+
location: {
|
|
87
|
+
location: send_node.location,
|
|
88
|
+
line: send_node.location.line,
|
|
89
|
+
column: send_node.location.column,
|
|
90
|
+
source: send_node.location.expression.source
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
# @return [Symbol] visibility
|
|
96
|
+
# @example :public, :private, :protected
|
|
97
|
+
def method_visibility(child_node)
|
|
98
|
+
visibility = :public
|
|
99
|
+
|
|
100
|
+
while child_node
|
|
101
|
+
if child_node.type == :send && ACCESS_CONTROL_KEYWORDS.include?(child_node.children[1])
|
|
102
|
+
visibility = child_node.children[1]
|
|
103
|
+
break
|
|
104
|
+
end
|
|
105
|
+
child_node = sibling_index(child_node).zero? ? nil : sibling_node(child_node)
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
visibility
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# @return [Integer] index of child_node in parent_node
|
|
112
|
+
def sibling_index(child_node)
|
|
113
|
+
class_body&.children&.index { |sibling| sibling.equal?(child_node) }
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
# @return [Parser::AST::Node] sibling node
|
|
117
|
+
def sibling_node(child_node)
|
|
118
|
+
class_body.children[sibling_index(child_node) - 1]
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# @return [Symbol] class name
|
|
122
|
+
def class_name
|
|
123
|
+
@class_name ||= class_details&.children&.last
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def shortened_file_path
|
|
127
|
+
@shortened_file_path ||= file_path&.split('/').last(2).join('/')
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PrivateDetective
|
|
4
|
+
|
|
5
|
+
class AnalyzeProject
|
|
6
|
+
|
|
7
|
+
attr_accessor :files, :report
|
|
8
|
+
|
|
9
|
+
# @param [Array] files
|
|
10
|
+
def initialize(files:)
|
|
11
|
+
@files = files
|
|
12
|
+
@report = {}
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def analyze_project
|
|
16
|
+
analyze_files
|
|
17
|
+
filter_report
|
|
18
|
+
show_report
|
|
19
|
+
|
|
20
|
+
# Exit the IRB session after the report is shown
|
|
21
|
+
exit
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
private
|
|
25
|
+
|
|
26
|
+
def analyze_files
|
|
27
|
+
files.each do |file_path|
|
|
28
|
+
analyze_file(file_path)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# @return [Hash] report with empty classes and methods removed
|
|
33
|
+
def filter_report
|
|
34
|
+
report.delete_if { |_class_name, methods| methods.empty? }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# @param [String] file_path
|
|
38
|
+
def analyze_file(file_path)
|
|
39
|
+
investigation = PrivateDetective::AnalyzeFile.new(file_path: file_path, report: report)
|
|
40
|
+
investigation.analyze_file
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def show_report
|
|
44
|
+
return puts "No class methods found".colorize(:red) if report.empty?
|
|
45
|
+
|
|
46
|
+
PrivateDetective::Report.generate(report: report)
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PrivateDetective
|
|
4
|
+
class Report
|
|
5
|
+
|
|
6
|
+
class << self
|
|
7
|
+
|
|
8
|
+
def generate(report:)
|
|
9
|
+
return puts 'No class methods found'.colorize(:red) if report.empty?
|
|
10
|
+
|
|
11
|
+
puts "Private Detective found the following Class method information in your project:\n".colorize(:yellow)
|
|
12
|
+
|
|
13
|
+
report.each do |class_name, methods|
|
|
14
|
+
puts "Class: #{class_name}".colorize(:cyan)
|
|
15
|
+
methods.each do |method_info|
|
|
16
|
+
method_name = method_info[:method]
|
|
17
|
+
|
|
18
|
+
puts " Method: #{method_name}, visibility: #{format_visibility(method_info[:visibility])}"
|
|
19
|
+
on_send_values = method_info[:on_send]
|
|
20
|
+
next if on_send_values.empty?
|
|
21
|
+
|
|
22
|
+
puts "\tMethod contents:"
|
|
23
|
+
handle_on_send_values(report, method_info)
|
|
24
|
+
end
|
|
25
|
+
puts "\n"
|
|
26
|
+
end
|
|
27
|
+
puts 'End of report'.colorize(:cyan)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def handle_on_send_values(report, method_info)
|
|
31
|
+
values = method_info[:on_send]
|
|
32
|
+
|
|
33
|
+
values.each do |_k, v|
|
|
34
|
+
method = v[:method]
|
|
35
|
+
klass = v[:class]
|
|
36
|
+
|
|
37
|
+
# find the parent method info
|
|
38
|
+
method_info = report[klass].find { |m| m[:method] == method }
|
|
39
|
+
next unless method_info
|
|
40
|
+
|
|
41
|
+
puts "\t\t#{method_info[:file_path]}:#{v.dig(:location, :line)}:#{v.dig(:location, :column)} #{klass.to_s.colorize(:cyan)}##{method.to_s.colorize(:cyan)} #{format_visibility(method_info[:visibility], true)}"
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def format_visibility(visibility, on_send = false)
|
|
46
|
+
case visibility
|
|
47
|
+
when :private
|
|
48
|
+
visibility.to_s.colorize(:red) + (on_send ? ' [Correctable]'.colorize(:green) : '')
|
|
49
|
+
when :protected
|
|
50
|
+
visibility.to_s.colorize(:yellow) + (on_send ? ' [Correctable]'.colorize(:green) : '')
|
|
51
|
+
else # :public
|
|
52
|
+
visibility.to_s.colorize(:green)
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# lib/private_detective.rb
|
|
2
|
+
|
|
3
|
+
# @example Usage in a Rails project
|
|
4
|
+
# private_detective = PrivateDetective::AnalyzeProject.new
|
|
5
|
+
# private_detective.analyze_project
|
|
6
|
+
|
|
7
|
+
module PrivateDetective
|
|
8
|
+
require "parser/current"
|
|
9
|
+
require "colorize"
|
|
10
|
+
require_relative "./private_detective/analyze_project"
|
|
11
|
+
require_relative "./private_detective/analyze_file"
|
|
12
|
+
require_relative "./private_detective/analyze_node"
|
|
13
|
+
require_relative "./private_detective/report"
|
|
14
|
+
require_relative "./private_detective/version"
|
|
15
|
+
|
|
16
|
+
class PrivateDetectiveError < StandardError; end
|
|
17
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/private_detective/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "private_detective"
|
|
7
|
+
spec.version = PrivateDetective::VERSION
|
|
8
|
+
spec.authors = ["rossme"]
|
|
9
|
+
spec.email = ["r.buddie@gmail.com"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Private Detective is a Ruby parser gem that helps you check method visibility."
|
|
12
|
+
spec.description = "Method visibility is an important part of Ruby's object model. Private Detective helps you check method visibility in your Ruby code."
|
|
13
|
+
spec.homepage = "https://github.com/rossme/private_detective"
|
|
14
|
+
spec.license = "MIT"
|
|
15
|
+
spec.required_ruby_version = ">= 2.6.0"
|
|
16
|
+
|
|
17
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
18
|
+
spec.metadata["source_code_uri"] = "https://github.com/rossme/private_detective"
|
|
19
|
+
spec.metadata["changelog_uri"] = "https://github.com/rossme/private_detective/blob/master/CHANGELOG.md"
|
|
20
|
+
|
|
21
|
+
# Specify which files should be added to the gem when it is released.
|
|
22
|
+
# The `git ls-files -z` loads the files in the RubyGem that have been added into git.
|
|
23
|
+
spec.files = Dir.chdir(__dir__) do
|
|
24
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
25
|
+
(File.expand_path(f) == __FILE__) || f.start_with?(*%w[bin/ test/ spec/ features/ .git .circleci appveyor])
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
spec.bindir = "bin"
|
|
29
|
+
spec.executables = ["private_detective"]
|
|
30
|
+
spec.require_paths = ["lib"]
|
|
31
|
+
|
|
32
|
+
# Uncomment to register a new dependency of your gem
|
|
33
|
+
# spec.add_dependency "example-gem", "~> 1.0"
|
|
34
|
+
|
|
35
|
+
spec.add_dependency "colorize", "~> 0.8.1"
|
|
36
|
+
|
|
37
|
+
# For more information and examples about making a new gem, check out our
|
|
38
|
+
# guide at: https://bundler.io/guides/creating_gem.html
|
|
39
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: private_detective
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- rossme
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2023-12-08 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: colorize
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: 0.8.1
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: 0.8.1
|
|
27
|
+
description: Method visibility is an important part of Ruby's object model. Private
|
|
28
|
+
Detective helps you check method visibility in your Ruby code.
|
|
29
|
+
email:
|
|
30
|
+
- r.buddie@gmail.com
|
|
31
|
+
executables:
|
|
32
|
+
- private_detective
|
|
33
|
+
extensions: []
|
|
34
|
+
extra_rdoc_files: []
|
|
35
|
+
files:
|
|
36
|
+
- ".idea/.gitignore"
|
|
37
|
+
- ".idea/misc.xml"
|
|
38
|
+
- ".idea/modules.xml"
|
|
39
|
+
- ".idea/private_detective.iml"
|
|
40
|
+
- ".idea/vcs.xml"
|
|
41
|
+
- ".rspec"
|
|
42
|
+
- ".rubocop.yml"
|
|
43
|
+
- CHANGELOG.md
|
|
44
|
+
- Gemfile
|
|
45
|
+
- Gemfile.lock
|
|
46
|
+
- LICENSE.txt
|
|
47
|
+
- README.md
|
|
48
|
+
- Rakefile
|
|
49
|
+
- bin/private_detective
|
|
50
|
+
- lib/private_detective.rb
|
|
51
|
+
- lib/private_detective/analyze_file.rb
|
|
52
|
+
- lib/private_detective/analyze_node.rb
|
|
53
|
+
- lib/private_detective/analyze_project.rb
|
|
54
|
+
- lib/private_detective/report.rb
|
|
55
|
+
- lib/private_detective/version.rb
|
|
56
|
+
- private_detective.gemspec
|
|
57
|
+
- sig/private_detective.rbs
|
|
58
|
+
homepage: https://github.com/rossme/private_detective
|
|
59
|
+
licenses:
|
|
60
|
+
- MIT
|
|
61
|
+
metadata:
|
|
62
|
+
homepage_uri: https://github.com/rossme/private_detective
|
|
63
|
+
source_code_uri: https://github.com/rossme/private_detective
|
|
64
|
+
changelog_uri: https://github.com/rossme/private_detective/blob/master/CHANGELOG.md
|
|
65
|
+
post_install_message:
|
|
66
|
+
rdoc_options: []
|
|
67
|
+
require_paths:
|
|
68
|
+
- lib
|
|
69
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
70
|
+
requirements:
|
|
71
|
+
- - ">="
|
|
72
|
+
- !ruby/object:Gem::Version
|
|
73
|
+
version: 2.6.0
|
|
74
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
|
+
requirements:
|
|
76
|
+
- - ">="
|
|
77
|
+
- !ruby/object:Gem::Version
|
|
78
|
+
version: '0'
|
|
79
|
+
requirements: []
|
|
80
|
+
rubygems_version: 3.4.10
|
|
81
|
+
signing_key:
|
|
82
|
+
specification_version: 4
|
|
83
|
+
summary: Private Detective is a Ruby parser gem that helps you check method visibility.
|
|
84
|
+
test_files: []
|