reek 2.1.0 → 2.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.rubocop.yml +2 -21
- data/.travis.yml +1 -0
- data/.yardopts +3 -6
- data/CHANGELOG +6 -0
- data/CONTRIBUTING.md +8 -3
- data/README.md +94 -42
- data/config/defaults.reek +0 -1
- data/docs/API.md +50 -0
- data/docs/Attribute.md +43 -0
- data/docs/Basic-Smell-Options.md +44 -0
- data/docs/Boolean-Parameter.md +52 -0
- data/docs/Class-Variable.md +40 -0
- data/docs/Code-Smells.md +34 -0
- data/docs/Command-Line-Options.md +84 -0
- data/docs/Configuration-Files.md +38 -0
- data/docs/Control-Couple.md +22 -0
- data/docs/Control-Parameter.md +29 -0
- data/docs/Data-Clump.md +44 -0
- data/docs/Duplicate-Method-Call.md +49 -0
- data/docs/Feature-Envy.md +29 -0
- data/docs/How-reek-works-internally.md +44 -0
- data/docs/Irresponsible-Module.md +39 -0
- data/docs/Large-Class.md +20 -0
- data/docs/Long-Parameter-List.md +38 -0
- data/docs/Long-Yield-List.md +36 -0
- data/docs/Module-Initialize.md +62 -0
- data/docs/Nested-Iterators.md +38 -0
- data/docs/Nil-Check.md +39 -0
- data/docs/Prima-Donna-Method.md +53 -0
- data/docs/RSpec-matchers.md +133 -0
- data/docs/Rake-Task.md +58 -0
- data/docs/Reek-Driven-Development.md +45 -0
- data/docs/Repeated-Conditional.md +44 -0
- data/docs/Simulated-Polymorphism.md +16 -0
- data/docs/Smell-Suppression.md +32 -0
- data/docs/Too-Many-Instance-Variables.md +43 -0
- data/docs/Too-Many-Methods.md +55 -0
- data/docs/Too-Many-Statements.md +50 -0
- data/docs/Uncommunicative-Method-Name.md +24 -0
- data/docs/Uncommunicative-Module-Name.md +23 -0
- data/docs/Uncommunicative-Name.md +16 -0
- data/docs/Uncommunicative-Parameter-Name.md +24 -0
- data/docs/Uncommunicative-Variable-Name.md +24 -0
- data/docs/Unused-Parameters.md +27 -0
- data/docs/Utility-Function.md +46 -0
- data/docs/Versioning-Policy.md +7 -0
- data/docs/YAML-Reports.md +111 -0
- data/docs/yard_plugin.rb +14 -0
- data/features/command_line_interface/options.feature +1 -0
- data/features/programmatic_access.feature +1 -1
- data/features/samples.feature +3 -3
- data/lib/reek.rb +2 -2
- data/lib/reek/cli/input.rb +2 -2
- data/lib/reek/cli/option_interpreter.rb +2 -0
- data/lib/reek/cli/options.rb +10 -4
- data/lib/reek/cli/reek_command.rb +2 -2
- data/lib/reek/cli/report/report.rb +60 -0
- data/lib/reek/cli/silencer.rb +13 -0
- data/lib/reek/{source → core}/ast_node.rb +1 -1
- data/lib/reek/{source → core}/ast_node_class_map.rb +10 -11
- data/lib/reek/{source → core}/code_comment.rb +1 -1
- data/lib/reek/core/code_context.rb +1 -1
- data/lib/reek/core/examiner.rb +85 -0
- data/lib/reek/core/method_context.rb +1 -1
- data/lib/reek/core/module_context.rb +2 -2
- data/lib/reek/core/reference_collector.rb +31 -0
- data/lib/reek/core/singleton_method_context.rb +0 -4
- data/lib/reek/core/smell_repository.rb +4 -2
- data/lib/reek/{source → core}/tree_dresser.rb +1 -1
- data/lib/reek/{source → sexp}/sexp_extensions.rb +5 -5
- data/lib/reek/sexp/sexp_formatter.rb +29 -0
- data/lib/reek/sexp/sexp_node.rb +91 -0
- data/lib/reek/smells.rb +4 -2
- data/lib/reek/smells/attribute.rb +35 -7
- data/lib/reek/smells/boolean_parameter.rb +1 -1
- data/lib/reek/smells/class_variable.rb +1 -1
- data/lib/reek/smells/control_parameter.rb +1 -1
- data/lib/reek/smells/data_clump.rb +1 -1
- data/lib/reek/smells/duplicate_method_call.rb +12 -4
- data/lib/reek/smells/feature_envy.rb +1 -1
- data/lib/reek/smells/irresponsible_module.rb +3 -3
- data/lib/reek/smells/long_parameter_list.rb +1 -1
- data/lib/reek/smells/long_yield_list.rb +1 -1
- data/lib/reek/smells/module_initialize.rb +1 -1
- data/lib/reek/smells/nested_iterators.rb +1 -1
- data/lib/reek/smells/nil_check.rb +3 -2
- data/lib/reek/smells/prima_donna_method.rb +18 -11
- data/lib/reek/smells/repeated_conditional.rb +3 -3
- data/lib/reek/smells/smell_detector.rb +5 -1
- data/lib/reek/smells/smell_warning.rb +99 -0
- data/lib/reek/smells/too_many_instance_variables.rb +1 -1
- data/lib/reek/smells/too_many_methods.rb +1 -1
- data/lib/reek/smells/too_many_statements.rb +1 -1
- data/lib/reek/smells/uncommunicative_method_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_module_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_parameter_name.rb +1 -1
- data/lib/reek/smells/uncommunicative_variable_name.rb +1 -1
- data/lib/reek/smells/unused_parameters.rb +1 -1
- data/lib/reek/smells/utility_function.rb +3 -16
- data/lib/reek/source/source_code.rb +31 -13
- data/lib/reek/source/source_locator.rb +16 -17
- data/lib/reek/source/source_repository.rb +10 -11
- data/lib/reek/spec/should_reek.rb +2 -2
- data/lib/reek/spec/should_reek_of.rb +2 -2
- data/lib/reek/spec/should_reek_only_of.rb +2 -2
- data/lib/reek/version.rb +1 -1
- data/reek.gemspec +3 -4
- data/spec/factories/factories.rb +1 -1
- data/spec/gem/yard_spec.rb +1 -1
- data/spec/quality/reek_source_spec.rb +2 -2
- data/spec/reek/cli/html_report_spec.rb +3 -3
- data/spec/reek/cli/json_report_spec.rb +3 -3
- data/spec/reek/cli/{option_interperter_spec.rb → option_interpreter_spec.rb} +1 -1
- data/spec/reek/cli/options_spec.rb +19 -0
- data/spec/reek/cli/text_report_spec.rb +7 -7
- data/spec/reek/cli/xml_report_spec.rb +34 -0
- data/spec/reek/cli/yaml_report_spec.rb +3 -3
- data/spec/reek/configuration/app_configuration_spec.rb +1 -1
- data/spec/reek/configuration/configuration_file_finder_spec.rb +22 -1
- data/spec/reek/{source → core}/code_comment_spec.rb +14 -14
- data/spec/reek/core/code_context_spec.rb +1 -1
- data/spec/reek/{examiner_spec.rb → core/examiner_spec.rb} +12 -12
- data/spec/reek/core/method_context_spec.rb +27 -22
- data/spec/reek/core/module_context_spec.rb +2 -2
- data/spec/reek/core/object_refs_spec.rb +1 -1
- data/spec/reek/{source → core}/object_source_spec.rb +1 -1
- data/spec/reek/{source → core}/reference_collector_spec.rb +25 -16
- data/spec/reek/core/singleton_method_context_spec.rb +12 -2
- data/spec/reek/core/smell_configuration_spec.rb +1 -1
- data/spec/reek/core/smell_repository_spec.rb +12 -1
- data/spec/reek/core/stop_context_spec.rb +1 -1
- data/spec/reek/core/tree_dresser_spec.rb +16 -0
- data/spec/reek/core/tree_walker_spec.rb +3 -3
- data/spec/reek/core/warning_collector_spec.rb +6 -6
- data/spec/reek/{source → sexp}/sexp_extensions_spec.rb +8 -8
- data/spec/reek/{source → sexp}/sexp_formatter_spec.rb +11 -5
- data/spec/reek/{source → sexp}/sexp_node_spec.rb +3 -3
- data/spec/reek/smells/attribute_spec.rb +89 -85
- data/spec/reek/smells/behaves_like_variable_detector.rb +1 -1
- data/spec/reek/smells/boolean_parameter_spec.rb +1 -1
- data/spec/reek/smells/class_variable_spec.rb +1 -1
- data/spec/reek/smells/control_parameter_spec.rb +1 -1
- data/spec/reek/smells/data_clump_spec.rb +2 -2
- data/spec/reek/smells/duplicate_method_call_spec.rb +1 -1
- data/spec/reek/smells/feature_envy_spec.rb +2 -2
- data/spec/reek/smells/irresponsible_module_spec.rb +1 -1
- data/spec/reek/smells/long_parameter_list_spec.rb +2 -2
- data/spec/reek/smells/long_yield_list_spec.rb +1 -1
- data/spec/reek/smells/module_initialize_spec.rb +1 -1
- data/spec/reek/smells/nested_iterators_spec.rb +2 -2
- data/spec/reek/smells/nil_check_spec.rb +1 -1
- data/spec/reek/smells/prima_donna_method_spec.rb +1 -1
- data/spec/reek/smells/repeated_conditional_spec.rb +1 -1
- data/spec/reek/smells/smell_detector_shared.rb +2 -2
- data/spec/reek/{smell_warning_spec.rb → smells/smell_warning_spec.rb} +7 -7
- data/spec/reek/smells/too_many_instance_variables_spec.rb +1 -1
- data/spec/reek/smells/too_many_methods_spec.rb +1 -1
- data/spec/reek/smells/too_many_statements_spec.rb +4 -4
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +1 -1
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +1 -1
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +1 -1
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +1 -1
- data/spec/reek/smells/unused_parameters_spec.rb +1 -1
- data/spec/reek/smells/utility_function_spec.rb +1 -1
- data/spec/reek/source/source_code_spec.rb +1 -1
- data/spec/reek/spec/should_reek_of_spec.rb +1 -1
- data/spec/reek/spec/should_reek_only_of_spec.rb +1 -1
- data/spec/reek/spec/should_reek_spec.rb +1 -1
- data/spec/samples/checkstyle.xml +2 -0
- data/spec/spec_helper.rb +15 -3
- metadata +68 -38
- data/.ruby-gemset +0 -1
- data/lib/reek/examiner.rb +0 -79
- data/lib/reek/smell_warning.rb +0 -87
- data/lib/reek/source/reference_collector.rb +0 -27
- data/lib/reek/source/sexp_formatter.rb +0 -22
- data/lib/reek/source/sexp_node.rb +0 -79
- data/spec/reek/source/tree_dresser_spec.rb +0 -16
@@ -1,5 +1,5 @@
|
|
1
1
|
require_relative 'command'
|
2
|
-
require_relative '../examiner'
|
2
|
+
require_relative '../core/examiner'
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module CLI
|
@@ -10,7 +10,7 @@ module Reek
|
|
10
10
|
class ReekCommand < Command
|
11
11
|
def execute(app)
|
12
12
|
@options.sources.each do |source|
|
13
|
-
reporter.add_examiner Examiner.new(source, smell_names)
|
13
|
+
reporter.add_examiner Core::Examiner.new(source, smell_names)
|
14
14
|
end
|
15
15
|
reporter.smells? ? app.report_smells : app.report_success
|
16
16
|
reporter.show
|
@@ -126,6 +126,66 @@ module Reek
|
|
126
126
|
print("Html file saved\n")
|
127
127
|
end
|
128
128
|
end
|
129
|
+
|
130
|
+
#
|
131
|
+
# Generates a list of smells in XML format
|
132
|
+
#
|
133
|
+
class XMLReport < Base
|
134
|
+
require 'rexml/document'
|
135
|
+
|
136
|
+
def initialize(options = {})
|
137
|
+
super options
|
138
|
+
end
|
139
|
+
|
140
|
+
def show
|
141
|
+
checkstyle = REXML::Element.new('checkstyle', document)
|
142
|
+
|
143
|
+
smells.group_by(&:source).each do |file, file_smells|
|
144
|
+
file_to_xml(file, file_smells, checkstyle)
|
145
|
+
end
|
146
|
+
|
147
|
+
print_xml(checkstyle.parent)
|
148
|
+
end
|
149
|
+
|
150
|
+
private
|
151
|
+
|
152
|
+
def document
|
153
|
+
REXML::Document.new.tap do |doc|
|
154
|
+
doc << REXML::XMLDecl.new
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def file_to_xml(file, file_smells, parent)
|
159
|
+
REXML::Element.new('file', parent).tap do |element|
|
160
|
+
element.attributes['name'] = File.realpath(file)
|
161
|
+
smells_to_xml(file_smells, element)
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def smells_to_xml(smells, parent)
|
166
|
+
smells.each do |smell|
|
167
|
+
smell_to_xml(smell, parent)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def smell_to_xml(smell, parent)
|
172
|
+
REXML::Element.new('error', parent).tap do |element|
|
173
|
+
attributes = [
|
174
|
+
['line', smell.lines.first],
|
175
|
+
['column', 0],
|
176
|
+
['severity', 'warning'],
|
177
|
+
['message', smell.message],
|
178
|
+
['source', smell.smell_type]
|
179
|
+
]
|
180
|
+
element.add_attributes(attributes)
|
181
|
+
end
|
182
|
+
end
|
183
|
+
|
184
|
+
def print_xml(document)
|
185
|
+
formatter = REXML::Formatters::Default.new
|
186
|
+
puts formatter.write(document, '')
|
187
|
+
end
|
188
|
+
end
|
129
189
|
end
|
130
190
|
end
|
131
191
|
end
|
@@ -1,9 +1,9 @@
|
|
1
1
|
require_relative 'ast_node'
|
2
|
-
require_relative 'sexp_node'
|
3
|
-
require_relative 'sexp_extensions'
|
2
|
+
require_relative '../sexp/sexp_node'
|
3
|
+
require_relative '../sexp/sexp_extensions'
|
4
4
|
|
5
5
|
module Reek
|
6
|
-
module
|
6
|
+
module Core
|
7
7
|
# Maps AST node types to sublasses of ASTNode extended with the relevant
|
8
8
|
# utility modules.
|
9
9
|
class ASTNodeClassMap
|
@@ -12,21 +12,20 @@ module Reek
|
|
12
12
|
end
|
13
13
|
|
14
14
|
def klass_for(type)
|
15
|
-
@klass_map[type] ||=
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
end
|
15
|
+
@klass_map[type] ||= Class.new(ASTNode).tap do |klass|
|
16
|
+
extension = extension_map[type]
|
17
|
+
klass.send :include, extension if extension
|
18
|
+
klass.send :include, Sexp::SexpNode
|
19
|
+
end
|
21
20
|
end
|
22
21
|
|
23
22
|
def extension_map
|
24
23
|
@extension_map ||=
|
25
24
|
begin
|
26
|
-
assoc = SexpExtensions.constants.map do |const|
|
25
|
+
assoc = Sexp::SexpExtensions.constants.map do |const|
|
27
26
|
[
|
28
27
|
const.to_s.sub(/Node$/, '').downcase.to_sym,
|
29
|
-
SexpExtensions.const_get(const)
|
28
|
+
Sexp::SexpExtensions.const_get(const)
|
30
29
|
]
|
31
30
|
end
|
32
31
|
Hash[assoc]
|
@@ -0,0 +1,85 @@
|
|
1
|
+
# NOTE: tree_walker is required first to ensure unparser is required before
|
2
|
+
# parser. This prevents a potentially incompatible version of parser from being
|
3
|
+
# loaded first. This is only relevant when running bin/reek straight from a
|
4
|
+
# checkout directory without using Bundler.
|
5
|
+
#
|
6
|
+
# See also https://github.com/troessner/reek/pull/468
|
7
|
+
require_relative 'tree_walker'
|
8
|
+
require_relative 'smell_repository'
|
9
|
+
require_relative 'warning_collector'
|
10
|
+
require_relative '../source/source_repository'
|
11
|
+
|
12
|
+
module Reek
|
13
|
+
module Core
|
14
|
+
#
|
15
|
+
# Applies all available smell detectors to a source.
|
16
|
+
#
|
17
|
+
class Examiner
|
18
|
+
#
|
19
|
+
# A simple description of the source being analysed for smells.
|
20
|
+
# If the source is a single File, this will be the file's path.
|
21
|
+
#
|
22
|
+
attr_accessor :description
|
23
|
+
|
24
|
+
#
|
25
|
+
# Creates an Examiner which scans the given +source+ for code smells.
|
26
|
+
#
|
27
|
+
# @param source [Source::SourceCode, Array<String>, #to_reek_source]
|
28
|
+
# If +source+ is a String it is assumed to be Ruby source code;
|
29
|
+
# if it is a File, the file is opened and parsed for Ruby source code;
|
30
|
+
# and if it is an Array, it is assumed to be a list of file paths,
|
31
|
+
# each of which is opened and parsed for source code.
|
32
|
+
#
|
33
|
+
def initialize(source, smell_types_to_filter_by = [])
|
34
|
+
@sources = Source::SourceRepository.parse(source)
|
35
|
+
@description = @sources.description
|
36
|
+
@collector = Core::WarningCollector.new
|
37
|
+
@smell_types = eligible_smell_types(smell_types_to_filter_by)
|
38
|
+
|
39
|
+
run
|
40
|
+
end
|
41
|
+
|
42
|
+
#
|
43
|
+
# @return [Array<SmellWarning>] the smells found in the source
|
44
|
+
#
|
45
|
+
def smells
|
46
|
+
@smells ||= @collector.warnings
|
47
|
+
end
|
48
|
+
|
49
|
+
#
|
50
|
+
# @return [Integer] the number of smells found in the source
|
51
|
+
#
|
52
|
+
def smells_count
|
53
|
+
smells.length
|
54
|
+
end
|
55
|
+
|
56
|
+
#
|
57
|
+
# @return [Boolean] true if and only if there are code smells in the source.
|
58
|
+
#
|
59
|
+
def smelly?
|
60
|
+
!smells.empty?
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def run
|
66
|
+
@sources.each do |source|
|
67
|
+
smell_repository = Core::SmellRepository.new(source.description, @smell_types)
|
68
|
+
syntax_tree = source.syntax_tree
|
69
|
+
Core::TreeWalker.new(smell_repository).process(syntax_tree) if syntax_tree
|
70
|
+
smell_repository.report_on(@collector)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def eligible_smell_types(smell_types_to_filter_by = [])
|
75
|
+
if smell_types_to_filter_by.any?
|
76
|
+
Core::SmellRepository.smell_types.select do |klass|
|
77
|
+
smell_types_to_filter_by.include? klass.smell_type
|
78
|
+
end
|
79
|
+
else
|
80
|
+
Core::SmellRepository.smell_types
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -1,5 +1,5 @@
|
|
1
1
|
require_relative 'code_context'
|
2
|
-
require_relative '../
|
2
|
+
require_relative '../sexp/sexp_formatter'
|
3
3
|
|
4
4
|
module Reek
|
5
5
|
module Core
|
@@ -9,7 +9,7 @@ module Reek
|
|
9
9
|
class ModuleContext < CodeContext
|
10
10
|
def initialize(outer, exp)
|
11
11
|
super(outer, exp)
|
12
|
-
@name =
|
12
|
+
@name = Sexp::SexpFormatter.format(exp.children.first)
|
13
13
|
end
|
14
14
|
|
15
15
|
def node_instance_methods
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Reek
|
2
|
+
module Core
|
3
|
+
#
|
4
|
+
# Locates references to the current object within a portion
|
5
|
+
# of an abstract syntax tree.
|
6
|
+
#
|
7
|
+
class ReferenceCollector
|
8
|
+
STOP_NODES = [:class, :module, :def, :defs]
|
9
|
+
|
10
|
+
def initialize(ast)
|
11
|
+
@ast = ast
|
12
|
+
end
|
13
|
+
|
14
|
+
def num_refs_to_self
|
15
|
+
(explicit_self_calls + implicit_self_calls).size
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def explicit_self_calls
|
21
|
+
[:self, :zsuper, :ivar, :ivasgn].flat_map do |node_type|
|
22
|
+
@ast.each_node(node_type, STOP_NODES)
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def implicit_self_calls
|
27
|
+
@ast.each_node(:send, STOP_NODES).reject(&:receiver)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -11,7 +11,7 @@ module Reek
|
|
11
11
|
attr_reader :detectors
|
12
12
|
|
13
13
|
def self.smell_types
|
14
|
-
Reek::Smells::SmellDetector.descendants
|
14
|
+
Reek::Smells::SmellDetector.descendants.sort_by(&:name)
|
15
15
|
end
|
16
16
|
|
17
17
|
def initialize(source_description = nil, smell_types = self.class.smell_types)
|
@@ -24,7 +24,9 @@ module Reek
|
|
24
24
|
end
|
25
25
|
|
26
26
|
def configure(klass, config)
|
27
|
-
|
27
|
+
detector = @detectors[klass]
|
28
|
+
raise ArgumentError, "Unknown smell type #{klass} found in configuration" unless detector
|
29
|
+
detector.configure_with(config)
|
28
30
|
end
|
29
31
|
|
30
32
|
def report_on(listener)
|
@@ -1,8 +1,8 @@
|
|
1
1
|
require_relative 'sexp_node'
|
2
|
-
require_relative 'reference_collector'
|
2
|
+
require_relative '../core/reference_collector'
|
3
3
|
|
4
4
|
module Reek
|
5
|
-
module
|
5
|
+
module Sexp
|
6
6
|
#
|
7
7
|
# Extension modules providing utility methods to ASTNode objects, depending
|
8
8
|
# on their type.
|
@@ -242,7 +242,7 @@ module Reek
|
|
242
242
|
end
|
243
243
|
|
244
244
|
def depends_on_instance?
|
245
|
-
ReferenceCollector.new(self).num_refs_to_self > 0
|
245
|
+
Core::ReferenceCollector.new(self).num_refs_to_self > 0
|
246
246
|
end
|
247
247
|
end
|
248
248
|
|
@@ -259,7 +259,7 @@ module Reek
|
|
259
259
|
include MethodNodeBase
|
260
260
|
def full_name(outer)
|
261
261
|
prefix = outer == '' ? '' : "#{outer}#"
|
262
|
-
"#{prefix}#{
|
262
|
+
"#{prefix}#{SexpFormatter.format(receiver)}.#{name}"
|
263
263
|
end
|
264
264
|
|
265
265
|
def depends_on_instance?
|
@@ -314,7 +314,7 @@ module Reek
|
|
314
314
|
end
|
315
315
|
|
316
316
|
def text_name
|
317
|
-
|
317
|
+
SexpFormatter.format(name)
|
318
318
|
end
|
319
319
|
end
|
320
320
|
|
@@ -0,0 +1,29 @@
|
|
1
|
+
require_relative '../cli/silencer'
|
2
|
+
Reek::CLI::Silencer.silently do
|
3
|
+
require 'unparser'
|
4
|
+
end
|
5
|
+
|
6
|
+
module Reek
|
7
|
+
module Sexp
|
8
|
+
#
|
9
|
+
# Formats snippets of syntax tree back into Ruby source code.
|
10
|
+
#
|
11
|
+
# :reek:DuplicateMethodCall { max_calls: 2 } is ok for lines.first
|
12
|
+
class SexpFormatter
|
13
|
+
# Formats the given sexp.
|
14
|
+
#
|
15
|
+
# sexp - S-expression of type AST::Node or something that is at least to_s-able.
|
16
|
+
#
|
17
|
+
# Returns a formatted string representation.
|
18
|
+
def self.format(sexp)
|
19
|
+
return sexp.to_s unless sexp.is_a? AST::Node
|
20
|
+
lines = Unparser.unparse(sexp).split "\n"
|
21
|
+
case lines.length
|
22
|
+
when 1 then lines.first
|
23
|
+
when 2 then lines.join('; ')
|
24
|
+
else [lines.first, lines.last].join(' ... ')
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
module Reek
|
2
|
+
module Sexp
|
3
|
+
#
|
4
|
+
# Extensions to +Sexp+ to allow +TreeWalker+ to navigate the abstract
|
5
|
+
# syntax tree more easily.
|
6
|
+
#
|
7
|
+
module SexpNode
|
8
|
+
#
|
9
|
+
# Carries out a depth-first traversal of this syntax tree, yielding
|
10
|
+
# every Sexp of type `target_type`. The traversal ignores any node
|
11
|
+
# whose type is listed in the Array `ignoring`.
|
12
|
+
# Takes a block as well.
|
13
|
+
#
|
14
|
+
# target_type - the type to look for, e.g. :send, :block
|
15
|
+
# ignoring - types to ignore, e.g. [:casgn, :class, :module]
|
16
|
+
# blk - block to execute for every hit
|
17
|
+
#
|
18
|
+
# Examples:
|
19
|
+
# context.each_node(:send, [:mlhs]) do |call_node| .... end
|
20
|
+
# context.each_node(:lvar).any? { |it| it.var_name == 'something' }
|
21
|
+
#
|
22
|
+
# Returns an array with all matching nodes.
|
23
|
+
def each_node(target_type, ignoring = [], &blk)
|
24
|
+
if block_given?
|
25
|
+
look_for_type(target_type, ignoring, &blk)
|
26
|
+
else
|
27
|
+
result = []
|
28
|
+
look_for_type(target_type, ignoring) { |exp| result << exp }
|
29
|
+
result
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
#
|
34
|
+
# Carries out a depth-first traversal of this syntax tree, yielding
|
35
|
+
# every Sexp of type `target_type`. The traversal ignores any node
|
36
|
+
# whose type is listed in the Array `ignoring`, including the top node.
|
37
|
+
# Takes a block as well.
|
38
|
+
#
|
39
|
+
# target_types - the types to look for, e.g. [:send, :block]
|
40
|
+
# ignoring - types to ignore, e.g. [:casgn, :class, :module]
|
41
|
+
# blk - block to execute for every hit
|
42
|
+
#
|
43
|
+
# Examples:
|
44
|
+
# exp.find_nodes([:block]).flat_map do |elem| ... end
|
45
|
+
#
|
46
|
+
# Returns an array with all matching nodes.
|
47
|
+
def find_nodes(target_types, ignoring = [])
|
48
|
+
result = []
|
49
|
+
look_for_types(target_types, ignoring) { |exp| result << exp }
|
50
|
+
result
|
51
|
+
end
|
52
|
+
|
53
|
+
def contains_nested_node?(target_type)
|
54
|
+
look_for_type(target_type) { |_elem| return true }
|
55
|
+
false
|
56
|
+
end
|
57
|
+
|
58
|
+
def format_to_ruby
|
59
|
+
SexpFormatter.format(self)
|
60
|
+
end
|
61
|
+
|
62
|
+
protected
|
63
|
+
|
64
|
+
# See ".each_node" for documentation.
|
65
|
+
def look_for_type(target_type, ignoring = [], &blk)
|
66
|
+
each_sexp do |elem|
|
67
|
+
elem.look_for_type(target_type, ignoring, &blk) unless ignoring.include?(elem.type)
|
68
|
+
end
|
69
|
+
blk.call(self) if type == target_type
|
70
|
+
end
|
71
|
+
|
72
|
+
# See ".find_nodes" for documentation.
|
73
|
+
def look_for_types(target_types, ignoring = [], &blk)
|
74
|
+
return if ignoring.include?(type)
|
75
|
+
if target_types.include? type
|
76
|
+
blk.call(self)
|
77
|
+
else
|
78
|
+
each_sexp do |elem|
|
79
|
+
elem.look_for_types(target_types, ignoring, &blk)
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
|
84
|
+
private
|
85
|
+
|
86
|
+
def each_sexp
|
87
|
+
children.each { |elem| yield elem if elem.is_a? AST::Node }
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|