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,81 @@
|
|
|
1
|
+
|
|
2
|
+
module Rubyzen
|
|
3
|
+
module Collections
|
|
4
|
+
# Collection of parsed file declarations. Serves as the top-level entry point
|
|
5
|
+
# for navigating into classes, modules, constants, and other code elements.
|
|
6
|
+
#
|
|
7
|
+
# @example Getting all controller classes
|
|
8
|
+
# project.files.with_paths('src/controllers/').classes
|
|
9
|
+
class FileCollection < BaseCollection
|
|
10
|
+
include Rubyzen::Providers::CollectionFilterProvider
|
|
11
|
+
|
|
12
|
+
# Filters files whose path includes any of the given substrings.
|
|
13
|
+
#
|
|
14
|
+
# @param paths [Array<String>] path substrings to match
|
|
15
|
+
# @return [FileCollection]
|
|
16
|
+
def with_paths(*paths)
|
|
17
|
+
filter do |file_declaration|
|
|
18
|
+
paths.any? { |p| file_declaration.path.include?(p) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Excludes files whose path includes any of the given substrings.
|
|
23
|
+
#
|
|
24
|
+
# @param paths [Array<String>] path substrings to exclude
|
|
25
|
+
# @return [FileCollection]
|
|
26
|
+
def without_paths(*paths)
|
|
27
|
+
filter do |file_declaration|
|
|
28
|
+
!paths.any? { |p| file_declaration.path.include?(p) }
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns all class declarations across every file.
|
|
33
|
+
#
|
|
34
|
+
# @return [ClassesCollection]
|
|
35
|
+
def classes
|
|
36
|
+
all_classes = flat_map(&:classes)
|
|
37
|
+
ClassesCollection.new(all_classes)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
# Returns all module declarations across every file.
|
|
41
|
+
#
|
|
42
|
+
# @return [ModulesCollection]
|
|
43
|
+
def modules
|
|
44
|
+
all_modules = flat_map(&:modules)
|
|
45
|
+
ModulesCollection.new(all_modules)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
# Returns all constant declarations across every file.
|
|
49
|
+
#
|
|
50
|
+
# @return [ConstantsCollection]
|
|
51
|
+
def constants
|
|
52
|
+
all_constants = flat_map(&:constants)
|
|
53
|
+
ConstantsCollection.new(all_constants)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# Returns all require/require_relative/load statements across every file.
|
|
57
|
+
#
|
|
58
|
+
# @return [RequiresCollection]
|
|
59
|
+
def requires
|
|
60
|
+
all_requires = flat_map(&:requires)
|
|
61
|
+
RequiresCollection.new(all_requires)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns all call sites across every file.
|
|
65
|
+
#
|
|
66
|
+
# @return [CallSiteCollection]
|
|
67
|
+
def call_sites
|
|
68
|
+
all_call_sites = flat_map(&:call_sites)
|
|
69
|
+
CallSiteCollection.new(all_call_sites)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Returns all block declarations across every file.
|
|
73
|
+
#
|
|
74
|
+
# @return [BlocksCollection]
|
|
75
|
+
def blocks
|
|
76
|
+
all_blocks = flat_map(&:blocks)
|
|
77
|
+
BlocksCollection.new(all_blocks)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of class-level macro invocations (e.g., +validates+, +has_many+, +before_action+).
|
|
4
|
+
#
|
|
5
|
+
# @example Filtering macros by name
|
|
6
|
+
# controllers.macros.with_name('before_action')
|
|
7
|
+
class MacrosCollection < BaseCollection
|
|
8
|
+
include Rubyzen::Providers::CollectionFilterProvider
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of method declarations with access to parameters,
|
|
4
|
+
# call sites, if statements, rescues, and raises within each method.
|
|
5
|
+
#
|
|
6
|
+
# @example Ensuring no method has more than 5 parameters
|
|
7
|
+
# controllers.all_methods.each { |m| expect(m.parameters.size).to be <= 5 }
|
|
8
|
+
class MethodsCollection < BaseCollection
|
|
9
|
+
include Rubyzen::Providers::CollectionFilterProvider
|
|
10
|
+
|
|
11
|
+
# Returns all parameters across every method.
|
|
12
|
+
#
|
|
13
|
+
# @return [ParametersCollection]
|
|
14
|
+
def parameters
|
|
15
|
+
ParametersCollection.new(
|
|
16
|
+
flat_map do |method|
|
|
17
|
+
method.parameters
|
|
18
|
+
end
|
|
19
|
+
)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Returns all if-statement declarations across every method.
|
|
23
|
+
#
|
|
24
|
+
# @return [DeclarationCollection]
|
|
25
|
+
def if_statements
|
|
26
|
+
DeclarationCollection.new(
|
|
27
|
+
flat_map do |method|
|
|
28
|
+
method.if_statements
|
|
29
|
+
end
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
# Returns all call sites across every method.
|
|
34
|
+
#
|
|
35
|
+
# @return [CallSiteCollection]
|
|
36
|
+
def call_sites
|
|
37
|
+
CallSiteCollection.new(
|
|
38
|
+
flat_map do |method|
|
|
39
|
+
method.call_sites
|
|
40
|
+
end
|
|
41
|
+
)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns all rescue declarations across every method.
|
|
45
|
+
#
|
|
46
|
+
# @return [RescuesCollection]
|
|
47
|
+
def rescues
|
|
48
|
+
RescuesCollection.new(
|
|
49
|
+
flat_map do |method|
|
|
50
|
+
method.rescues
|
|
51
|
+
end
|
|
52
|
+
)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns all raise declarations across every method.
|
|
56
|
+
#
|
|
57
|
+
# @return [RaisesCollection]
|
|
58
|
+
def raises
|
|
59
|
+
RaisesCollection.new(
|
|
60
|
+
flat_map do |method|
|
|
61
|
+
method.raises
|
|
62
|
+
end
|
|
63
|
+
)
|
|
64
|
+
end
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
end
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of module declarations with methods for navigating into
|
|
4
|
+
# child elements (methods, classes, constants).
|
|
5
|
+
#
|
|
6
|
+
# @example Getting all methods defined in modules
|
|
7
|
+
# project.files.modules.all_methods
|
|
8
|
+
class ModulesCollection < BaseCollection
|
|
9
|
+
include Rubyzen::Providers::CollectionFilterProvider
|
|
10
|
+
|
|
11
|
+
# Returns all methods defined across every module.
|
|
12
|
+
#
|
|
13
|
+
# @return [MethodsCollection]
|
|
14
|
+
def all_methods
|
|
15
|
+
all_methods = flat_map(&:all_methods)
|
|
16
|
+
MethodsCollection.new(all_methods)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Returns all class declarations nested inside every module.
|
|
20
|
+
#
|
|
21
|
+
# @return [ClassesCollection]
|
|
22
|
+
def classes
|
|
23
|
+
all_classes = flat_map(&:classes)
|
|
24
|
+
ClassesCollection.new(all_classes)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# Returns all constant declarations across every module.
|
|
28
|
+
#
|
|
29
|
+
# @return [ConstantsCollection]
|
|
30
|
+
def constants
|
|
31
|
+
all_constants = flat_map(&:constants)
|
|
32
|
+
ConstantsCollection.new(all_constants)
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of method parameter declarations.
|
|
4
|
+
#
|
|
5
|
+
# @example Filtering parameters by name
|
|
6
|
+
# controllers.all_methods.parameters.with_name('id')
|
|
7
|
+
class ParametersCollection < BaseCollection
|
|
8
|
+
include Rubyzen::Providers::CollectionFilterProvider
|
|
9
|
+
end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of raise declarations found in methods or classes.
|
|
4
|
+
#
|
|
5
|
+
# @example Ensuring no plain-string raises in controllers
|
|
6
|
+
# expect(controllers.raises.with_string).to zen_empty
|
|
7
|
+
class RaisesCollection < BaseCollection
|
|
8
|
+
# Filters raises that use a plain string message (not an exception class).
|
|
9
|
+
#
|
|
10
|
+
# @return [RaisesCollection]
|
|
11
|
+
def with_string
|
|
12
|
+
filter(&:with_string?)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Filters raises that include the given exception class.
|
|
16
|
+
#
|
|
17
|
+
# @param exception_class [String] the exception class name to match
|
|
18
|
+
# @return [RaisesCollection]
|
|
19
|
+
def with_exception_type(exception_class)
|
|
20
|
+
filter do |raise_declaration|
|
|
21
|
+
raise_declaration.exception_types.include?(exception_class)
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of require/require_relative/load statements found in files.
|
|
4
|
+
#
|
|
5
|
+
# @example Ensuring controllers do not use require_relative
|
|
6
|
+
# expect(controller_files.requires.require_relative_calls).to zen_empty
|
|
7
|
+
class RequiresCollection < BaseCollection
|
|
8
|
+
include Rubyzen::Providers::CollectionFilterProvider
|
|
9
|
+
|
|
10
|
+
# Returns only +require+ calls.
|
|
11
|
+
#
|
|
12
|
+
# @return [RequiresCollection]
|
|
13
|
+
def require_calls
|
|
14
|
+
filter(&:require?)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
# Returns only +require_relative+ calls.
|
|
18
|
+
#
|
|
19
|
+
# @return [RequiresCollection]
|
|
20
|
+
def require_relative_calls
|
|
21
|
+
filter(&:require_relative?)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Returns only +load+ calls.
|
|
25
|
+
#
|
|
26
|
+
# @return [RequiresCollection]
|
|
27
|
+
def load_calls
|
|
28
|
+
filter(&:load?)
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Collections
|
|
3
|
+
# Collection of rescue declarations found in methods or classes.
|
|
4
|
+
#
|
|
5
|
+
# @example Checking for StandardError rescues
|
|
6
|
+
# project.files.classes.all_methods.rescues.with_exception_type('StandardError')
|
|
7
|
+
class RescuesCollection < BaseCollection
|
|
8
|
+
# Filters rescues that handle the given exception class.
|
|
9
|
+
#
|
|
10
|
+
# @param exception_class [String] the exception class name to match
|
|
11
|
+
# @return [RescuesCollection]
|
|
12
|
+
def with_exception_type(exception_class)
|
|
13
|
+
filter do |rescue_declaration|
|
|
14
|
+
rescue_declaration.exception_types.include?(exception_class)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents an +attr_reader+, +attr_writer+, or +attr_accessor+ declaration.
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# attr = klass.attributes.first
|
|
7
|
+
# attr.name #=> "attr_reader"
|
|
8
|
+
# attr.symbols #=> ["name", "email"]
|
|
9
|
+
# attr.reader? #=> true
|
|
10
|
+
# attr.private? #=> false
|
|
11
|
+
#
|
|
12
|
+
class AttributeDeclaration
|
|
13
|
+
include Rubyzen::Providers::FilePathProvider
|
|
14
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
15
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
16
|
+
include Rubyzen::Providers::VisibilityProvider
|
|
17
|
+
|
|
18
|
+
# @return [RuboCop::AST::Node]
|
|
19
|
+
attr_reader :node
|
|
20
|
+
|
|
21
|
+
# @return [ClassDeclaration, ModuleDeclaration]
|
|
22
|
+
attr_reader :parent_class
|
|
23
|
+
alias :parent :parent_class
|
|
24
|
+
|
|
25
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
26
|
+
# @param parent_class [ClassDeclaration, ModuleDeclaration] the parent declaration
|
|
27
|
+
def initialize(node, parent_class)
|
|
28
|
+
@node = node
|
|
29
|
+
@parent_class = parent_class
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Returns the attribute type name.
|
|
33
|
+
#
|
|
34
|
+
# @return [String] one of +"attr_reader"+, +"attr_writer"+, +"attr_accessor"+
|
|
35
|
+
def name
|
|
36
|
+
node.method_name.to_s
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Returns the declared symbol names.
|
|
40
|
+
#
|
|
41
|
+
# @return [Array<String>] e.g. +["name", "email"]+
|
|
42
|
+
def symbols
|
|
43
|
+
node.arguments.map { |arg| arg.value.to_s if arg.type == :sym }.compact
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
# @return [Boolean] true for +attr_reader+ and +attr_accessor+
|
|
47
|
+
def reader?
|
|
48
|
+
%w[attr_reader attr_accessor].include?(name)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# @return [Boolean] true for +attr_writer+ and +attr_accessor+
|
|
52
|
+
def writer?
|
|
53
|
+
%w[attr_writer attr_accessor].include?(name)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# @return [Boolean] true only for +attr_accessor+
|
|
57
|
+
def accessor?
|
|
58
|
+
name == 'attr_accessor'
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
|
62
|
+
end
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a Ruby block (+do...end+ or +{ }+).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# block = method.blocks.first
|
|
7
|
+
# block.method_name #=> "each"
|
|
8
|
+
# block.call_sites #=> CallSiteCollection
|
|
9
|
+
# block.lines_of_code #=> 5
|
|
10
|
+
#
|
|
11
|
+
class BlockDeclaration
|
|
12
|
+
include Rubyzen::Providers::FilePathProvider
|
|
13
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
14
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
15
|
+
include Rubyzen::Providers::LinesOfCodeProvider
|
|
16
|
+
include Rubyzen::Providers::RescuesProvider
|
|
17
|
+
include Rubyzen::Providers::RaisesProvider
|
|
18
|
+
include Rubyzen::Providers::SourceCodeProvider
|
|
19
|
+
include Rubyzen::Providers::CallSiteProvider
|
|
20
|
+
|
|
21
|
+
# @return [RuboCop::AST::Node]
|
|
22
|
+
attr_reader :node
|
|
23
|
+
|
|
24
|
+
# @return [MethodDeclaration, FileDeclaration]
|
|
25
|
+
attr_reader :parent
|
|
26
|
+
|
|
27
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
28
|
+
# @param parent [MethodDeclaration, FileDeclaration] the parent declaration
|
|
29
|
+
def initialize(node, parent)
|
|
30
|
+
@node = node
|
|
31
|
+
@parent = parent
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# Returns the method name the block is passed to. Alias for {#method_name}.
|
|
35
|
+
#
|
|
36
|
+
# @return [String]
|
|
37
|
+
def name
|
|
38
|
+
method_name
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Returns the method name the block is passed to.
|
|
42
|
+
#
|
|
43
|
+
# @return [String] e.g. +"each"+, +"map"+, +"let"+
|
|
44
|
+
def method_name
|
|
45
|
+
node.method_name.to_s
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a method call site (a +send+ node in the AST).
|
|
4
|
+
#
|
|
5
|
+
# @example
|
|
6
|
+
# call_site = method.call_sites.first
|
|
7
|
+
# call_site.method_name #=> "find"
|
|
8
|
+
# call_site.receiver #=> "User"
|
|
9
|
+
# call_site.keyword_args #=> [:id, :name]
|
|
10
|
+
#
|
|
11
|
+
class CallSiteDeclaration
|
|
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, FileDeclaration]
|
|
21
|
+
attr_reader :parent
|
|
22
|
+
|
|
23
|
+
# @param node [RuboCop::AST::Node] the AST node
|
|
24
|
+
# @param parent [MethodDeclaration, BlockDeclaration, FileDeclaration] the parent declaration
|
|
25
|
+
def initialize(node, parent)
|
|
26
|
+
@node = node
|
|
27
|
+
@parent = parent
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns the called method name. Alias for {#method_name}.
|
|
31
|
+
#
|
|
32
|
+
# @return [String]
|
|
33
|
+
def name
|
|
34
|
+
method_name
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# Returns the constant name of the receiver, if any.
|
|
38
|
+
#
|
|
39
|
+
# @return [String, nil] e.g. +"User"+ for +User.find(1)+, +nil+ for +save+
|
|
40
|
+
def receiver
|
|
41
|
+
node.receiver&.type == :const ? node.receiver.const_name : nil
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
# Returns the called method name.
|
|
45
|
+
#
|
|
46
|
+
# @return [String]
|
|
47
|
+
def method_name
|
|
48
|
+
node.method_name.to_s
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Returns the keyword argument keys passed in the call.
|
|
52
|
+
#
|
|
53
|
+
# @return [Array<Symbol>] e.g. +[:level, :details]+
|
|
54
|
+
def keyword_args
|
|
55
|
+
node.arguments.flat_map do |arg|
|
|
56
|
+
next [] unless arg.hash_type?
|
|
57
|
+
|
|
58
|
+
arg.pairs.filter_map do |pair|
|
|
59
|
+
pair.key.value if pair.key.type == :sym
|
|
60
|
+
end
|
|
61
|
+
end.uniq
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
# Returns a hash mapping keyword argument keys to their literal values.
|
|
65
|
+
#
|
|
66
|
+
# @return [Hash{Symbol => Object}] values are +nil+ for non-literal expressions
|
|
67
|
+
def keyword_arg_value_pairs
|
|
68
|
+
result = {}
|
|
69
|
+
node.arguments.each do |arg|
|
|
70
|
+
next unless arg.hash_type?
|
|
71
|
+
|
|
72
|
+
arg.pairs.each do |pair|
|
|
73
|
+
next unless pair.key.type == :sym
|
|
74
|
+
|
|
75
|
+
value_node = pair.value
|
|
76
|
+
result[pair.key.value] = value_node.respond_to?(:value) ? value_node.value : nil
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
result
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
# Returns positional symbol arguments.
|
|
83
|
+
#
|
|
84
|
+
# @return [Array<Symbol>] e.g. +[:name, :email]+
|
|
85
|
+
def symbols
|
|
86
|
+
node.arguments.select { |arg| arg.type == :sym }.map(&:value)
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
# Returns positional string arguments.
|
|
90
|
+
#
|
|
91
|
+
# @return [Array<String>]
|
|
92
|
+
def strings
|
|
93
|
+
node.arguments.select { |arg| arg.type == :str }.map(&:value)
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
module Rubyzen
|
|
2
|
+
module Declarations
|
|
3
|
+
# Represents a Ruby class definition. Provides access to methods, attributes,
|
|
4
|
+
# macros, and other class-level constructs.
|
|
5
|
+
#
|
|
6
|
+
# @example
|
|
7
|
+
# klass = file.classes.first
|
|
8
|
+
# klass.name #=> "Admin::UsersController"
|
|
9
|
+
# klass.superclass_name #=> "ApplicationController"
|
|
10
|
+
# klass.instance_methods #=> MethodsCollection
|
|
11
|
+
#
|
|
12
|
+
class ClassDeclaration
|
|
13
|
+
include Rubyzen::Providers::IfStatementsProvider
|
|
14
|
+
include Rubyzen::Providers::BlocksProvider
|
|
15
|
+
include Rubyzen::Providers::FilePathProvider
|
|
16
|
+
include Rubyzen::Providers::LineNumberProvider
|
|
17
|
+
include Rubyzen::Providers::LinesOfCodeProvider
|
|
18
|
+
include Rubyzen::Providers::ClassNameProvider
|
|
19
|
+
include Rubyzen::Providers::ConstantsProvider
|
|
20
|
+
include Rubyzen::Providers::AttributesProvider
|
|
21
|
+
include Rubyzen::Providers::MacrosProvider
|
|
22
|
+
include Rubyzen::Providers::RescuesProvider
|
|
23
|
+
include Rubyzen::Providers::RaisesProvider
|
|
24
|
+
|
|
25
|
+
# @return [RuboCop::AST::Node] the class AST node
|
|
26
|
+
attr_reader :node
|
|
27
|
+
|
|
28
|
+
# @return [FileDeclaration] the file this class belongs to
|
|
29
|
+
attr_reader :file_declaration
|
|
30
|
+
|
|
31
|
+
# @param node [RuboCop::AST::Node]
|
|
32
|
+
# @param file_declaration [FileDeclaration]
|
|
33
|
+
def initialize(node, file_declaration)
|
|
34
|
+
@node = node
|
|
35
|
+
@file_declaration = file_declaration
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
# Returns the fully-qualified class name including parent modules.
|
|
39
|
+
#
|
|
40
|
+
# @return [String] e.g. +"Admin::UsersController"+
|
|
41
|
+
def name
|
|
42
|
+
parent_module_names = []
|
|
43
|
+
current_node = node.parent
|
|
44
|
+
|
|
45
|
+
while current_node
|
|
46
|
+
if current_node.type == :module
|
|
47
|
+
parent_module_names.unshift(current_node.identifier&.const_name)
|
|
48
|
+
end
|
|
49
|
+
current_node = current_node.parent
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
[parent_module_names, name_without_modules].flatten.compact.join('::')
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the class name without module prefixes.
|
|
56
|
+
#
|
|
57
|
+
# @return [String] e.g. +"UsersController"+
|
|
58
|
+
def name_without_modules
|
|
59
|
+
node.identifier&.const_name
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
# Returns the superclass name, if any.
|
|
63
|
+
#
|
|
64
|
+
# @return [String, nil] e.g. +"ApplicationController"+
|
|
65
|
+
def superclass_name
|
|
66
|
+
super_node = node.children[1]
|
|
67
|
+
return nil unless super_node&.type == :const
|
|
68
|
+
|
|
69
|
+
super_node.const_name
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
# Checks whether the superclass name starts with the given prefix.
|
|
73
|
+
#
|
|
74
|
+
# @param prefix [String]
|
|
75
|
+
# @return [Boolean]
|
|
76
|
+
def superclass_prefix?(prefix)
|
|
77
|
+
superclass_name&.start_with?(prefix)
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# Returns instance methods defined directly in this class.
|
|
81
|
+
#
|
|
82
|
+
# @return [Collections::MethodsCollection]
|
|
83
|
+
def instance_methods
|
|
84
|
+
Collections::MethodsCollection.new(
|
|
85
|
+
instance_method_nodes.map do |def_node|
|
|
86
|
+
MethodDeclaration.new(def_node, self)
|
|
87
|
+
end
|
|
88
|
+
)
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
# Returns class methods (both +self.method+ and +class << self+ styles).
|
|
92
|
+
#
|
|
93
|
+
# @return [Collections::MethodsCollection]
|
|
94
|
+
def class_methods
|
|
95
|
+
Collections::MethodsCollection.new(
|
|
96
|
+
class_method_nodes.map do |method_node|
|
|
97
|
+
MethodDeclaration.new(method_node, self)
|
|
98
|
+
end
|
|
99
|
+
)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
# Returns unique method names called anywhere in this class.
|
|
103
|
+
#
|
|
104
|
+
# @return [Array<String>]
|
|
105
|
+
def called_method_names
|
|
106
|
+
node.each_descendant(:send).map { |send_node| send_node.method_name.to_s }.uniq
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
# Returns the top-level module name from the enclosing file.
|
|
110
|
+
#
|
|
111
|
+
# @return [String, nil]
|
|
112
|
+
def top_level_module
|
|
113
|
+
file_declaration.top_level_module_name
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
private
|
|
117
|
+
|
|
118
|
+
def class_body_node
|
|
119
|
+
node.children[2]
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def class_body_children
|
|
123
|
+
body = class_body_node
|
|
124
|
+
return [] unless body
|
|
125
|
+
|
|
126
|
+
body.type == :begin ? body.child_nodes : [body]
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def instance_method_nodes
|
|
130
|
+
class_body_children.select { |child| child.type == :def }
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def class_defs_nodes
|
|
134
|
+
class_body_children.select do |child|
|
|
135
|
+
child.type == :defs && child.children[0]&.type == :self
|
|
136
|
+
end
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
def class_method_nodes
|
|
140
|
+
class_defs_nodes + class_sclass_def_nodes
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def class_sclass_def_nodes
|
|
144
|
+
class_body_children
|
|
145
|
+
.select { |child| singleton_class_node?(child) }
|
|
146
|
+
.flat_map do |child|
|
|
147
|
+
body_children(child.children[1]).select do |body_child|
|
|
148
|
+
method_node?(body_child)
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
def singleton_class_node?(child)
|
|
154
|
+
child.type == :sclass && child.children[0]&.type == :self
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def body_children(body)
|
|
158
|
+
return [] unless body
|
|
159
|
+
|
|
160
|
+
body.type == :begin ? body.child_nodes : [body]
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
def method_node?(child)
|
|
164
|
+
%i[def defs].include?(child.type)
|
|
165
|
+
end
|
|
166
|
+
end
|
|
167
|
+
end
|
|
168
|
+
end
|