rubyzen-lint 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/LICENSE +201 -0
- data/README.md +110 -0
- data/lib/rubyzen/cache/parse_cache.rb +36 -0
- data/lib/rubyzen/collections/attributes_collection.rb +32 -0
- data/lib/rubyzen/collections/base_collection.rb +30 -0
- data/lib/rubyzen/collections/blocks_collection.rb +27 -0
- data/lib/rubyzen/collections/call_site_collection.rb +52 -0
- data/lib/rubyzen/collections/classes_collection.rb +77 -0
- data/lib/rubyzen/collections/constants_collection.rb +11 -0
- data/lib/rubyzen/collections/declaration_collection.rb +11 -0
- data/lib/rubyzen/collections/file_collection.rb +81 -0
- data/lib/rubyzen/collections/macros_collection.rb +11 -0
- data/lib/rubyzen/collections/methods_collection.rb +67 -0
- data/lib/rubyzen/collections/modules_collection.rb +36 -0
- data/lib/rubyzen/collections/parameters_collection.rb +11 -0
- data/lib/rubyzen/collections/raises_collection.rb +26 -0
- data/lib/rubyzen/collections/requires_collection.rb +32 -0
- data/lib/rubyzen/collections/rescues_collection.rb +19 -0
- data/lib/rubyzen/declarations/attribute_declaration.rb +62 -0
- data/lib/rubyzen/declarations/block_declaration.rb +49 -0
- data/lib/rubyzen/declarations/call_site_declaration.rb +98 -0
- data/lib/rubyzen/declarations/class_declaration.rb +168 -0
- data/lib/rubyzen/declarations/constant_declaration.rb +155 -0
- data/lib/rubyzen/declarations/file_declaration.rb +69 -0
- data/lib/rubyzen/declarations/if_statement_declaration.rb +44 -0
- data/lib/rubyzen/declarations/macro_declaration.rb +81 -0
- data/lib/rubyzen/declarations/method_declaration.rb +63 -0
- data/lib/rubyzen/declarations/module_declaration.rb +115 -0
- data/lib/rubyzen/declarations/parameter_declaration.rb +43 -0
- data/lib/rubyzen/declarations/raise_declaration.rb +87 -0
- data/lib/rubyzen/declarations/require_declaration.rb +61 -0
- data/lib/rubyzen/declarations/rescue_declaration.rb +54 -0
- data/lib/rubyzen/lint.rb +1 -0
- data/lib/rubyzen/matchers/matcher_helpers.rb +176 -0
- data/lib/rubyzen/matchers/zen_empty_matcher.rb +54 -0
- data/lib/rubyzen/matchers/zen_false_matcher.rb +63 -0
- data/lib/rubyzen/matchers/zen_true_matcher.rb +57 -0
- data/lib/rubyzen/parsers/a_s_t_parser.rb +33 -0
- data/lib/rubyzen/project.rb +69 -0
- data/lib/rubyzen/providers/attributes_provider.rb +19 -0
- data/lib/rubyzen/providers/blocks_provider.rb +15 -0
- data/lib/rubyzen/providers/call_site_provider.rb +15 -0
- data/lib/rubyzen/providers/class_name_provider.rb +36 -0
- data/lib/rubyzen/providers/collection_filter_provider.rb +64 -0
- data/lib/rubyzen/providers/constants_provider.rb +17 -0
- data/lib/rubyzen/providers/file_path_provider.rb +26 -0
- data/lib/rubyzen/providers/if_statements_provider.rb +11 -0
- data/lib/rubyzen/providers/line_number_provider.rb +11 -0
- data/lib/rubyzen/providers/lines_of_code_provider.rb +11 -0
- data/lib/rubyzen/providers/macros_provider.rb +17 -0
- data/lib/rubyzen/providers/raises_provider.rb +19 -0
- data/lib/rubyzen/providers/requires_provider.rb +19 -0
- data/lib/rubyzen/providers/rescues_provider.rb +17 -0
- data/lib/rubyzen/providers/source_code_provider.rb +11 -0
- data/lib/rubyzen/providers/visibility_provider.rb +49 -0
- data/lib/rubyzen/version.rb +3 -0
- data/lib/rubyzen-lint.rb +1 -0
- data/lib/rubyzen.rb +98 -0
- data/rubyzen-lint.gemspec +28 -0
- metadata +148 -0
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a constant assignment (+MAX = 100+) or reference (+MAX+).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# const = file.constants.filter(&:assignment?).first
|
|
7
|
+
# const.name #=> "MAX"
|
|
8
|
+
# const.value #=> 100
|
|
9
|
+
# const.top_level? #=> true
|
|
10
|
+
#
|
|
11
|
+
class ConstantDeclaration
|
|
12
|
+
include Rubyzen::Providers::FilePathProvider
|
|
13
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
14
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
15
|
+
include Rubyzen::Providers::SourceCodeProvider
|
|
16
|
+
|
|
17
|
+
# @return [RuboCop::AST::Node]
|
|
18
|
+
attr_reader :node
|
|
19
|
+
|
|
20
|
+
# @return [FileDeclaration, ClassDeclaration, ModuleDeclaration]
|
|
21
|
+
attr_reader :parent
|
|
22
|
+
|
|
23
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
24
|
+
# @param parent [FileDeclaration, ClassDeclaration, ModuleDeclaration] the parent declaration
|
|
25
|
+
def initialize(node, parent)
|
|
26
|
+
@node = node
|
|
27
|
+
@parent = parent
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the constant name.
|
|
31
|
+
#
|
|
32
|
+
# @return [String]
|
|
33
|
+
def name
|
|
34
|
+
case node.type
|
|
35
|
+
when :casgn
|
|
36
|
+
node.children[1].to_s
|
|
37
|
+
when :const
|
|
38
|
+
node.const_name
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns the assigned value for constant assignments.
|
|
43
|
+
#
|
|
44
|
+
# @return [String, Integer, Float, Boolean, nil]
|
|
45
|
+
def value
|
|
46
|
+
return nil unless assignment?
|
|
47
|
+
|
|
48
|
+
value_node = node.children[2]
|
|
49
|
+
return nil unless value_node
|
|
50
|
+
|
|
51
|
+
case value_node.type
|
|
52
|
+
when :str
|
|
53
|
+
value_node.str_content
|
|
54
|
+
when :int
|
|
55
|
+
value_node.children[0]
|
|
56
|
+
when :float
|
|
57
|
+
value_node.children[0]
|
|
58
|
+
when :true, :false
|
|
59
|
+
value_node.type == :true
|
|
60
|
+
else
|
|
61
|
+
value_node.source
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Returns whether this is a constant assignment (+:casgn+).
|
|
66
|
+
#
|
|
67
|
+
# @return [Boolean]
|
|
68
|
+
def assignment?
|
|
69
|
+
node.type == :casgn
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns whether this is a constant reference (+:const+).
|
|
73
|
+
#
|
|
74
|
+
# @return [Boolean]
|
|
75
|
+
def reference?
|
|
76
|
+
node.type == :const
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
# Returns whether this constant is defined at file scope (not inside a class or module).
|
|
80
|
+
#
|
|
81
|
+
# @return [Boolean]
|
|
82
|
+
def top_level?
|
|
83
|
+
return false unless parent.is_a?(Rubyzen::Declarations::FileDeclaration)
|
|
84
|
+
|
|
85
|
+
current_node = node
|
|
86
|
+
while current_node
|
|
87
|
+
current_node = current_node.parent
|
|
88
|
+
return false if current_node && (current_node.type == :class || current_node.type == :module)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
true
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the enclosing {ClassDeclaration}, if any.
|
|
95
|
+
#
|
|
96
|
+
# @return [ClassDeclaration, nil]
|
|
97
|
+
def enclosing_class
|
|
98
|
+
find_enclosing_ast_node(:class) do |n|
|
|
99
|
+
Rubyzen::Declarations::ClassDeclaration.new(n, file_declaration)
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns whether this constant is defined inside a class.
|
|
104
|
+
#
|
|
105
|
+
# @return [Boolean]
|
|
106
|
+
def in_class?
|
|
107
|
+
!enclosing_class.nil?
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns the enclosing {ModuleDeclaration}, if any.
|
|
111
|
+
#
|
|
112
|
+
# @return [ModuleDeclaration, nil]
|
|
113
|
+
def enclosing_module
|
|
114
|
+
find_enclosing_ast_node(:module) do |n|
|
|
115
|
+
Rubyzen::Declarations::ModuleDeclaration.new(n, file_declaration)
|
|
116
|
+
end
|
|
117
|
+
end
|
|
118
|
+
|
|
119
|
+
# Returns whether this constant is defined inside a module.
|
|
120
|
+
#
|
|
121
|
+
# @return [Boolean]
|
|
122
|
+
def in_module?
|
|
123
|
+
!enclosing_module.nil?
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Returns whether this constant is defined inside a class or module.
|
|
127
|
+
#
|
|
128
|
+
# @return [Boolean]
|
|
129
|
+
def scoped?
|
|
130
|
+
!top_level?
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
private
|
|
134
|
+
|
|
135
|
+
def file_declaration
|
|
136
|
+
current = parent
|
|
137
|
+
while current
|
|
138
|
+
return current if current.is_a?(Rubyzen::Declarations::FileDeclaration)
|
|
139
|
+
return current.file_declaration if current.respond_to?(:file_declaration)
|
|
140
|
+
current = current.respond_to?(:parent) ? current.parent : nil
|
|
141
|
+
end
|
|
142
|
+
nil
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def find_enclosing_ast_node(type)
|
|
146
|
+
current_node = node.parent
|
|
147
|
+
while current_node
|
|
148
|
+
return yield(current_node) if current_node.type == type
|
|
149
|
+
current_node = current_node.respond_to?(:parent) ? current_node.parent : nil
|
|
150
|
+
end
|
|
151
|
+
nil
|
|
152
|
+
end
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a parsed Ruby source file. This is the root of the declaration
|
|
4
|
+
# hierarchy — all other declarations are accessed through a FileDeclaration.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# project = Rubyzen::Project.new("/app/src")
|
|
8
|
+
# file = project.files.first
|
|
9
|
+
# file.name #=> "user.rb"
|
|
10
|
+
# file.classes #=> [ClassDeclaration, ...]
|
|
11
|
+
#
|
|
12
|
+
class FileDeclaration
|
|
13
|
+
include Rubyzen::Providers::FilePathProvider
|
|
14
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
15
|
+
include Rubyzen::Providers::LinesOfCodeProvider
|
|
16
|
+
include Rubyzen::Providers::ConstantsProvider
|
|
17
|
+
include Rubyzen::Providers::RequiresProvider
|
|
18
|
+
include Rubyzen::Providers::CallSiteProvider
|
|
19
|
+
include Rubyzen::Providers::BlocksProvider
|
|
20
|
+
|
|
21
|
+
# @return [String] absolute path to the source file
|
|
22
|
+
attr_reader :path
|
|
23
|
+
|
|
24
|
+
# @return [RuboCop::AST::Node] the root AST node
|
|
25
|
+
attr_reader :node
|
|
26
|
+
alias :ast :node
|
|
27
|
+
|
|
28
|
+
# @param path [String] absolute file path
|
|
29
|
+
# @param ast [RuboCop::AST::Node] parsed AST root node
|
|
30
|
+
def initialize(path, ast)
|
|
31
|
+
@path = path
|
|
32
|
+
@node = ast
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the basename of the file.
|
|
36
|
+
#
|
|
37
|
+
# @return [String] e.g. +"user.rb"+
|
|
38
|
+
def name
|
|
39
|
+
File.basename(path)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
# Returns all classes defined in this file.
|
|
43
|
+
#
|
|
44
|
+
# @return [Array<ClassDeclaration>]
|
|
45
|
+
def classes
|
|
46
|
+
node.each_node(:class).map do |class_node|
|
|
47
|
+
ClassDeclaration.new(class_node, self)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the name of the first module in the file, used to determine
|
|
52
|
+
# the top-level namespace.
|
|
53
|
+
#
|
|
54
|
+
# @return [String, nil]
|
|
55
|
+
def top_level_module_name
|
|
56
|
+
modules.first&.name_without_modules
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# Returns all modules defined in this file.
|
|
60
|
+
#
|
|
61
|
+
# @return [Array<ModuleDeclaration>]
|
|
62
|
+
def modules
|
|
63
|
+
node.each_node(:module).map do |module_node|
|
|
64
|
+
Rubyzen::Declarations::ModuleDeclaration.new(module_node, self)
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents an +if+ / +unless+ statement within a method or class.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# if_stmt = method.if_statements.first
|
|
7
|
+
# if_stmt.condition_source #=> "user.active?"
|
|
8
|
+
# if_stmt.source_code #=> "if user.active?\n ..."
|
|
9
|
+
#
|
|
10
|
+
class IfStatementDeclaration
|
|
11
|
+
include Rubyzen::Providers::FilePathProvider
|
|
12
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
13
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
14
|
+
include Rubyzen::Providers::SourceCodeProvider
|
|
15
|
+
|
|
16
|
+
# @return [RuboCop::AST::Node]
|
|
17
|
+
attr_reader :node
|
|
18
|
+
|
|
19
|
+
# @return [MethodDeclaration, ClassDeclaration]
|
|
20
|
+
attr_reader :parent
|
|
21
|
+
|
|
22
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
23
|
+
# @param parent [MethodDeclaration, ClassDeclaration] the parent declaration
|
|
24
|
+
def initialize(node, parent)
|
|
25
|
+
@node = node
|
|
26
|
+
@parent = parent
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
# Returns the raw source of the condition expression.
|
|
30
|
+
#
|
|
31
|
+
# @return [String, nil]
|
|
32
|
+
def condition_source
|
|
33
|
+
node.condition&.source
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
# Returns the name of the parent declaration.
|
|
37
|
+
#
|
|
38
|
+
# @return [String]
|
|
39
|
+
def name
|
|
40
|
+
parent.name
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a class-level macro call (e.g. +validates_required+, +belongs_to+).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# macro = klass.macros.first
|
|
7
|
+
# macro.name #=> "validates_required"
|
|
8
|
+
# macro.symbols #=> [:name, :email]
|
|
9
|
+
# macro.keyword_args #=> [:foreign_key, :optional]
|
|
10
|
+
#
|
|
11
|
+
class MacroDeclaration
|
|
12
|
+
include Rubyzen::Providers::FilePathProvider
|
|
13
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
14
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
15
|
+
include Rubyzen::Providers::SourceCodeProvider
|
|
16
|
+
|
|
17
|
+
# @return [RuboCop::AST::Node]
|
|
18
|
+
attr_reader :node
|
|
19
|
+
|
|
20
|
+
# @return [ClassDeclaration]
|
|
21
|
+
attr_reader :parent
|
|
22
|
+
|
|
23
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
24
|
+
# @param parent [ClassDeclaration] the parent declaration
|
|
25
|
+
def initialize(node, parent)
|
|
26
|
+
@node = node
|
|
27
|
+
@parent = parent
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the macro method name.
|
|
31
|
+
#
|
|
32
|
+
# @return [String] e.g. +"validates_required"+, +"belongs_to"+
|
|
33
|
+
def name
|
|
34
|
+
node.method_name.to_s
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns positional symbol arguments.
|
|
38
|
+
#
|
|
39
|
+
# @return [Array<Symbol>]
|
|
40
|
+
def symbols
|
|
41
|
+
node.arguments.select { |arg| arg.type == :sym }.map(&:value)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns positional string arguments.
|
|
45
|
+
#
|
|
46
|
+
# @return [Array<String>]
|
|
47
|
+
def strings
|
|
48
|
+
node.arguments.select { |arg| arg.type == :str }.map(&:value)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns keyword argument keys.
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Symbol>]
|
|
54
|
+
def keyword_args
|
|
55
|
+
extract_keyword_args(node)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
# Returns the constant receiver, if any.
|
|
59
|
+
#
|
|
60
|
+
# @return [String, nil] e.g. +"Config"+ for +Config.setting+
|
|
61
|
+
def receiver
|
|
62
|
+
node.receiver&.type == :const ? node.receiver.const_name : nil
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
private
|
|
66
|
+
|
|
67
|
+
def extract_keyword_args(send_node)
|
|
68
|
+
send_node.arguments.flat_map do |arg|
|
|
69
|
+
if arg.hash_type?
|
|
70
|
+
arg.each_pair.map do |pair|
|
|
71
|
+
key_node = pair.key
|
|
72
|
+
key_node.type == :sym ? key_node.value : nil
|
|
73
|
+
end.compact
|
|
74
|
+
else
|
|
75
|
+
[]
|
|
76
|
+
end
|
|
77
|
+
end.uniq
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a Ruby method definition (+def+ or +def self.+).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# method = klass.instance_methods.first
|
|
7
|
+
# method.name #=> "calculate"
|
|
8
|
+
# method.parameters? #=> true
|
|
9
|
+
# method.call_sites #=> CallSiteCollection
|
|
10
|
+
# method.visibility #=> :private
|
|
11
|
+
#
|
|
12
|
+
class MethodDeclaration
|
|
13
|
+
include Rubyzen::Providers::IfStatementsProvider
|
|
14
|
+
include Rubyzen::Providers::BlocksProvider
|
|
15
|
+
include Rubyzen::Providers::FilePathProvider
|
|
16
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
17
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
18
|
+
include Rubyzen::Providers::ConstantsProvider
|
|
19
|
+
include Rubyzen::Providers::CallSiteProvider
|
|
20
|
+
include Rubyzen::Providers::LinesOfCodeProvider
|
|
21
|
+
include Rubyzen::Providers::VisibilityProvider
|
|
22
|
+
include Rubyzen::Providers::RescuesProvider
|
|
23
|
+
include Rubyzen::Providers::RaisesProvider
|
|
24
|
+
|
|
25
|
+
# @return [RuboCop::AST::Node]
|
|
26
|
+
attr_reader :node
|
|
27
|
+
|
|
28
|
+
# @return [ClassDeclaration, ModuleDeclaration]
|
|
29
|
+
attr_reader :parent_class
|
|
30
|
+
alias :parent :parent_class
|
|
31
|
+
|
|
32
|
+
def initialize(node, parent_class)
|
|
33
|
+
@node = node
|
|
34
|
+
@parent_class = parent_class
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the method name.
|
|
38
|
+
#
|
|
39
|
+
# @return [String]
|
|
40
|
+
def name
|
|
41
|
+
node.method_name.to_s
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns the method's parameters.
|
|
45
|
+
#
|
|
46
|
+
# @return [Collections::ParametersCollection]
|
|
47
|
+
def parameters
|
|
48
|
+
Collections::ParametersCollection.new(
|
|
49
|
+
node.arguments.map do |arg|
|
|
50
|
+
ParameterDeclaration.new(arg, self)
|
|
51
|
+
end
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns whether this method has any parameters.
|
|
56
|
+
#
|
|
57
|
+
# @return [Boolean]
|
|
58
|
+
def parameters?
|
|
59
|
+
node.arguments.any?
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a Ruby module definition.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# mod = file.modules.first
|
|
7
|
+
# mod.name #=> "Admin::Api"
|
|
8
|
+
# mod.all_methods #=> MethodsCollection
|
|
9
|
+
# mod.classes #=> [ClassDeclaration, ...]
|
|
10
|
+
#
|
|
11
|
+
class ModuleDeclaration
|
|
12
|
+
include Rubyzen::Providers::FilePathProvider
|
|
13
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
14
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
15
|
+
include Rubyzen::Providers::ConstantsProvider
|
|
16
|
+
include Rubyzen::Providers::LinesOfCodeProvider
|
|
17
|
+
include Rubyzen::Providers::AttributesProvider
|
|
18
|
+
|
|
19
|
+
# @return [RuboCop::AST::Node]
|
|
20
|
+
attr_reader :node
|
|
21
|
+
|
|
22
|
+
# @return [FileDeclaration]
|
|
23
|
+
attr_reader :file_declaration
|
|
24
|
+
|
|
25
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
26
|
+
# @param file_declaration [FileDeclaration] the parent file declaration
|
|
27
|
+
def initialize(node, file_declaration)
|
|
28
|
+
@node = node
|
|
29
|
+
@file_declaration = file_declaration
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the fully-qualified module name including parent modules.
|
|
33
|
+
#
|
|
34
|
+
# @return [String] e.g. +"Admin::Api"+
|
|
35
|
+
def name
|
|
36
|
+
parent_module_names = []
|
|
37
|
+
current_node = node.parent
|
|
38
|
+
|
|
39
|
+
while current_node
|
|
40
|
+
if current_node.type == :module
|
|
41
|
+
parent_module_names.unshift(current_node.identifier&.const_name)
|
|
42
|
+
end
|
|
43
|
+
current_node = current_node.parent
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
[parent_module_names, name_without_modules].flatten.compact.join('::')
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Returns the module name without parent module prefixes.
|
|
50
|
+
#
|
|
51
|
+
# @return [String]
|
|
52
|
+
def name_without_modules
|
|
53
|
+
node.identifier&.const_name
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns nested modules within this module.
|
|
57
|
+
#
|
|
58
|
+
# @return [Array<ModuleDeclaration>]
|
|
59
|
+
def modules
|
|
60
|
+
node.each_descendant(:module).map { |mod_node| ModuleDeclaration.new(mod_node, file_declaration) }
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
# Returns classes defined within this module.
|
|
64
|
+
#
|
|
65
|
+
# @return [Array<ClassDeclaration>]
|
|
66
|
+
def classes
|
|
67
|
+
node.each_node(:class).map do |class_node|
|
|
68
|
+
ClassDeclaration.new(class_node, file_declaration)
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns methods defined directly in this module.
|
|
73
|
+
#
|
|
74
|
+
# @return [Collections::MethodsCollection]
|
|
75
|
+
def all_methods
|
|
76
|
+
Collections::MethodsCollection.new(
|
|
77
|
+
direct_method_nodes.map { |method_node| MethodDeclaration.new(method_node, self) }
|
|
78
|
+
)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def module_body_node
|
|
84
|
+
node.children[1]
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def module_body_children
|
|
88
|
+
body = module_body_node
|
|
89
|
+
return [] unless body
|
|
90
|
+
|
|
91
|
+
body.type == :begin ? body.child_nodes : [body]
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def direct_method_nodes
|
|
95
|
+
direct_nodes = module_body_children.select do |child|
|
|
96
|
+
%i[def defs].include?(child.type)
|
|
97
|
+
end
|
|
98
|
+
singleton_nodes = module_body_children.flat_map do |child|
|
|
99
|
+
next [] unless child.type == :sclass
|
|
100
|
+
next [] unless child.children[0]&.type == :self
|
|
101
|
+
|
|
102
|
+
sclass_body = child.children[1]
|
|
103
|
+
next [] unless sclass_body
|
|
104
|
+
|
|
105
|
+
sclass_children = sclass_body.type == :begin ? sclass_body.child_nodes : [sclass_body]
|
|
106
|
+
sclass_children.select do |sclass_child|
|
|
107
|
+
%i[def defs].include?(sclass_child.type)
|
|
108
|
+
end
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
direct_nodes + singleton_nodes
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a method parameter.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# param = method.parameters.first
|
|
7
|
+
# param.name #=> :user_id
|
|
8
|
+
# param.default_value #=> 42
|
|
9
|
+
#
|
|
10
|
+
class ParameterDeclaration
|
|
11
|
+
include Rubyzen::Providers::FilePathProvider
|
|
12
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
13
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
14
|
+
|
|
15
|
+
# @return [RuboCop::AST::Node]
|
|
16
|
+
attr_reader :node
|
|
17
|
+
|
|
18
|
+
# @return [MethodDeclaration]
|
|
19
|
+
attr_reader :parent
|
|
20
|
+
|
|
21
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
22
|
+
# @param parent [MethodDeclaration] the parent declaration
|
|
23
|
+
def initialize(node, parent)
|
|
24
|
+
@node = node
|
|
25
|
+
@parent = parent
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Returns the parameter name.
|
|
29
|
+
#
|
|
30
|
+
# @return [Symbol]
|
|
31
|
+
def name
|
|
32
|
+
node.name
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Returns the default value if one is defined.
|
|
36
|
+
#
|
|
37
|
+
# @return [Object, nil]
|
|
38
|
+
def default_value
|
|
39
|
+
node.children[1]&.value
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a +raise+ statement.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# raise_decl = method.raises.first
|
|
7
|
+
# raise_decl.exception_types #=> ["ArgumentError"]
|
|
8
|
+
# raise_decl.message #=> "invalid input"
|
|
9
|
+
# raise_decl.with_string? #=> false
|
|
10
|
+
#
|
|
11
|
+
class RaiseDeclaration
|
|
12
|
+
include Rubyzen::Providers::FilePathProvider
|
|
13
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
14
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
15
|
+
include Rubyzen::Providers::SourceCodeProvider
|
|
16
|
+
|
|
17
|
+
# @return [RuboCop::AST::Node]
|
|
18
|
+
attr_reader :node
|
|
19
|
+
|
|
20
|
+
# @return [MethodDeclaration, BlockDeclaration]
|
|
21
|
+
attr_reader :parent
|
|
22
|
+
|
|
23
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
24
|
+
# @param parent [MethodDeclaration, BlockDeclaration] the parent declaration
|
|
25
|
+
def initialize(node, parent)
|
|
26
|
+
@node = node
|
|
27
|
+
@parent = parent
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the exception class names being raised.
|
|
31
|
+
# Defaults to +["RuntimeError"]+ for bare +raise+ or +raise "message"+.
|
|
32
|
+
#
|
|
33
|
+
# @return [Array<String>]
|
|
34
|
+
def exception_types
|
|
35
|
+
extract_exception_types
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns whether the raise uses a bare string (+raise "message"+).
|
|
39
|
+
#
|
|
40
|
+
# @return [Boolean]
|
|
41
|
+
def with_string?
|
|
42
|
+
first_arg = node.arguments.first
|
|
43
|
+
first_arg&.type == :str
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# Returns the error message string, if any.
|
|
47
|
+
#
|
|
48
|
+
# @return [String, nil]
|
|
49
|
+
def message
|
|
50
|
+
extract_message
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
private
|
|
54
|
+
|
|
55
|
+
def extract_exception_types
|
|
56
|
+
first_arg = node.arguments.first
|
|
57
|
+
|
|
58
|
+
return ['RuntimeError'] if first_arg.nil? || first_arg.type == :str
|
|
59
|
+
|
|
60
|
+
if first_arg.type == :const
|
|
61
|
+
[first_arg.const_name]
|
|
62
|
+
elsif first_arg.type == :send && first_arg.method_name == :new
|
|
63
|
+
receiver = first_arg.receiver
|
|
64
|
+
receiver&.type == :const ? [receiver.const_name] : ['RuntimeError']
|
|
65
|
+
else
|
|
66
|
+
['RuntimeError']
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def extract_message
|
|
71
|
+
first_arg = node.arguments.first
|
|
72
|
+
second_arg = node.arguments[1]
|
|
73
|
+
|
|
74
|
+
if first_arg&.type == :str
|
|
75
|
+
first_arg.value
|
|
76
|
+
elsif second_arg&.type == :str
|
|
77
|
+
second_arg.value
|
|
78
|
+
elsif first_arg&.type == :send && first_arg.method_name == :new
|
|
79
|
+
msg_arg = first_arg.arguments.first
|
|
80
|
+
msg_arg&.type == :str ? msg_arg.value : nil
|
|
81
|
+
else
|
|
82
|
+
nil
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
end
|