reek 2.1.0 → 2.2.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 +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
|