reek 1.2.6 → 1.2.8
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.
- data/.yardopts +10 -0
- data/History.txt +62 -0
- data/README.md +74 -0
- data/bin/reek +2 -2
- data/config/defaults.reek +44 -5
- data/features/api.feature +20 -0
- data/features/masking_smells.feature +60 -42
- data/features/options.feature +7 -2
- data/features/rake_task.feature +29 -22
- data/features/reports.feature +9 -41
- data/features/samples.feature +184 -196
- data/features/stdin.feature +5 -8
- data/features/step_definitions/reek_steps.rb +8 -4
- data/features/support/env.rb +2 -3
- data/features/yaml.feature +85 -0
- data/lib/reek/cli/application.rb +46 -0
- data/lib/reek/cli/command_line.rb +108 -0
- data/lib/reek/cli/help_command.rb +18 -0
- data/lib/reek/cli/reek_command.rb +37 -0
- data/lib/reek/cli/report.rb +58 -0
- data/lib/reek/cli/version_command.rb +19 -0
- data/lib/reek/cli/yaml_command.rb +32 -0
- data/lib/reek/core/code_context.rb +64 -0
- data/lib/reek/core/code_parser.rb +166 -0
- data/lib/reek/core/method_context.rb +84 -0
- data/lib/reek/core/module_context.rb +20 -0
- data/lib/reek/core/object_refs.rb +51 -0
- data/lib/reek/core/singleton_method_context.rb +20 -0
- data/lib/reek/core/smell_configuration.rb +62 -0
- data/lib/reek/core/sniffer.rb +105 -0
- data/lib/reek/core/stop_context.rb +30 -0
- data/lib/reek/core/warning_collector.rb +27 -0
- data/lib/reek/examiner.rb +104 -0
- data/lib/reek/rake/task.rb +142 -0
- data/lib/reek/smell_warning.rb +73 -23
- data/lib/reek/smells/attribute.rb +26 -21
- data/lib/reek/smells/boolean_parameter.rb +38 -0
- data/lib/reek/smells/class_variable.rb +22 -9
- data/lib/reek/smells/control_couple.rb +40 -16
- data/lib/reek/smells/data_clump.rb +114 -23
- data/lib/reek/smells/duplication.rb +55 -15
- data/lib/reek/smells/feature_envy.rb +16 -7
- data/lib/reek/smells/irresponsible_module.rb +37 -0
- data/lib/reek/smells/large_class.rb +37 -19
- data/lib/reek/smells/long_method.rb +20 -9
- data/lib/reek/smells/long_parameter_list.rb +22 -10
- data/lib/reek/smells/long_yield_list.rb +43 -7
- data/lib/reek/smells/nested_iterators.rb +68 -8
- data/lib/reek/smells/simulated_polymorphism.rb +26 -15
- data/lib/reek/smells/smell_detector.rb +29 -51
- data/lib/reek/smells/uncommunicative_method_name.rb +74 -0
- data/lib/reek/smells/uncommunicative_module_name.rb +74 -0
- data/lib/reek/smells/uncommunicative_parameter_name.rb +79 -0
- data/lib/reek/smells/uncommunicative_variable_name.rb +89 -0
- data/lib/reek/smells/utility_function.rb +51 -12
- data/lib/reek/smells.rb +29 -0
- data/lib/reek/source/code_comment.rb +37 -0
- data/lib/reek/source/config_file.rb +72 -0
- data/lib/reek/source/core_extras.rb +46 -0
- data/lib/reek/source/reference_collector.rb +28 -0
- data/lib/reek/source/sexp_formatter.rb +17 -0
- data/lib/reek/source/source_code.rb +44 -0
- data/lib/reek/source/source_file.rb +32 -0
- data/lib/reek/source/source_locator.rb +42 -0
- data/lib/reek/source/tree_dresser.rb +204 -0
- data/lib/reek/source.rb +18 -0
- data/lib/reek/spec/should_reek.rb +31 -0
- data/lib/reek/spec/should_reek_of.rb +37 -0
- data/lib/reek/spec/should_reek_only_of.rb +37 -0
- data/lib/reek/spec.rb +51 -0
- data/lib/reek.rb +8 -4
- data/reek.gemspec +9 -7
- data/spec/matchers/smell_of_matcher.rb +58 -0
- data/spec/reek/{help_command_spec.rb → cli/help_command_spec.rb} +3 -4
- data/spec/reek/cli/reek_command_spec.rb +46 -0
- data/spec/reek/cli/report_spec.rb +30 -0
- data/spec/reek/{version_command_spec.rb → cli/version_command_spec.rb} +3 -3
- data/spec/reek/cli/yaml_command_spec.rb +47 -0
- data/spec/reek/core/code_context_spec.rb +145 -0
- data/spec/reek/{code_parser_spec.rb → core/code_parser_spec.rb} +7 -6
- data/spec/reek/{config_spec.rb → core/config_spec.rb} +2 -6
- data/spec/reek/{method_context_spec.rb → core/method_context_spec.rb} +16 -36
- data/spec/reek/core/module_context_spec.rb +27 -0
- data/spec/reek/{object_refs_spec.rb → core/object_refs_spec.rb} +5 -6
- data/spec/reek/core/singleton_method_context_spec.rb +9 -0
- data/spec/reek/core/smell_configuration_spec.rb +11 -0
- data/spec/reek/core/stop_context_spec.rb +17 -0
- data/spec/reek/core/warning_collector_spec.rb +27 -0
- data/spec/reek/examiner_spec.rb +103 -0
- data/spec/reek/smell_warning_spec.rb +74 -82
- data/spec/reek/smells/attribute_spec.rb +42 -23
- data/spec/reek/smells/behaves_like_variable_detector.rb +2 -2
- data/spec/reek/smells/boolean_parameter_spec.rb +66 -0
- data/spec/reek/smells/class_variable_spec.rb +60 -75
- data/spec/reek/smells/control_couple_spec.rb +37 -32
- data/spec/reek/smells/data_clump_spec.rb +63 -24
- data/spec/reek/smells/duplication_spec.rb +106 -57
- data/spec/reek/smells/feature_envy_spec.rb +113 -92
- data/spec/reek/smells/irresponsible_module_spec.rb +58 -0
- data/spec/reek/smells/large_class_spec.rb +67 -57
- data/spec/reek/smells/long_method_spec.rb +40 -10
- data/spec/reek/smells/long_parameter_list_spec.rb +50 -28
- data/spec/reek/smells/long_yield_list_spec.rb +57 -0
- data/spec/reek/smells/nested_iterators_spec.rb +115 -7
- data/spec/reek/smells/simulated_polymorphism_spec.rb +56 -20
- data/spec/reek/smells/smell_detector_shared.rb +42 -0
- data/spec/reek/smells/uncommunicative_method_name_spec.rb +42 -0
- data/spec/reek/smells/uncommunicative_module_name_spec.rb +66 -0
- data/spec/reek/smells/uncommunicative_parameter_name_spec.rb +71 -0
- data/spec/reek/smells/uncommunicative_variable_name_spec.rb +120 -0
- data/spec/reek/smells/utility_function_spec.rb +50 -4
- data/spec/reek/source/code_comment_spec.rb +82 -0
- data/spec/reek/source/object_source_spec.rb +20 -0
- data/spec/reek/source/reference_collector_spec.rb +53 -0
- data/spec/reek/{adapters/source_spec.rb → source/source_code_spec.rb} +7 -8
- data/spec/reek/source/tree_dresser_spec.rb +270 -0
- data/spec/reek/spec/should_reek_of_spec.rb +76 -0
- data/spec/reek/spec/should_reek_only_of_spec.rb +89 -0
- data/spec/reek/{adapters → spec}/should_reek_spec.rb +8 -32
- data/spec/samples/all_but_one_masked/clean_one.rb +1 -0
- data/spec/samples/all_but_one_masked/dirty.rb +1 -0
- data/spec/samples/all_but_one_masked/masked.reek +5 -1
- data/spec/samples/clean_due_to_masking/clean_one.rb +1 -0
- data/spec/samples/clean_due_to_masking/clean_three.rb +1 -0
- data/spec/samples/clean_due_to_masking/clean_two.rb +1 -0
- data/spec/samples/clean_due_to_masking/dirty_one.rb +1 -1
- data/spec/samples/clean_due_to_masking/dirty_two.rb +1 -1
- data/spec/samples/clean_due_to_masking/masked.reek +5 -1
- data/spec/samples/config/allow_duplication.reek +3 -0
- data/spec/samples/config/deeper_nested_iterators.reek +3 -0
- data/spec/samples/corrupt_config_file/dirty.rb +1 -1
- data/spec/samples/demo/demo.rb +8 -0
- data/spec/samples/empty_config_file/dirty.rb +2 -1
- data/spec/samples/exceptions.reek +1 -1
- data/spec/samples/inline_config/dirty.rb +16 -0
- data/spec/samples/inline_config/masked.reek +7 -0
- data/spec/samples/mask_some/dirty.rb +8 -0
- data/spec/samples/mask_some/some.reek +8 -0
- data/spec/samples/masked/dirty.rb +2 -1
- data/spec/samples/masked/masked.reek +3 -1
- data/spec/samples/mixed_results/clean_one.rb +1 -0
- data/spec/samples/mixed_results/clean_three.rb +1 -0
- data/spec/samples/mixed_results/clean_two.rb +1 -0
- data/spec/samples/mixed_results/dirty_one.rb +1 -0
- data/spec/samples/mixed_results/dirty_two.rb +1 -0
- data/spec/samples/not_quite_masked/dirty.rb +2 -1
- data/spec/samples/not_quite_masked/masked.reek +1 -1
- data/spec/samples/overrides/masked/dirty.rb +2 -1
- data/spec/samples/overrides/masked/lower.reek +3 -1
- data/spec/samples/three_clean_files/clean_one.rb +1 -0
- data/spec/samples/three_clean_files/clean_three.rb +1 -0
- data/spec/samples/three_clean_files/clean_two.rb +1 -0
- data/spec/samples/two_smelly_files/dirty_one.rb +2 -1
- data/spec/samples/two_smelly_files/dirty_two.rb +2 -1
- data/spec/spec_helper.rb +10 -2
- data/tasks/reek.rake +2 -2
- data/tasks/test.rake +12 -3
- metadata +121 -79
- data/README.rdoc +0 -84
- data/features/profile.feature +0 -34
- data/lib/reek/adapters/application.rb +0 -46
- data/lib/reek/adapters/command_line.rb +0 -77
- data/lib/reek/adapters/config_file.rb +0 -31
- data/lib/reek/adapters/core_extras.rb +0 -64
- data/lib/reek/adapters/rake_task.rb +0 -121
- data/lib/reek/adapters/report.rb +0 -86
- data/lib/reek/adapters/source.rb +0 -72
- data/lib/reek/adapters/spec.rb +0 -133
- data/lib/reek/block_context.rb +0 -62
- data/lib/reek/class_context.rb +0 -41
- data/lib/reek/code_context.rb +0 -68
- data/lib/reek/code_parser.rb +0 -203
- data/lib/reek/configuration.rb +0 -57
- data/lib/reek/detector_stack.rb +0 -37
- data/lib/reek/help_command.rb +0 -14
- data/lib/reek/if_context.rb +0 -18
- data/lib/reek/masking_collection.rb +0 -33
- data/lib/reek/method_context.rb +0 -138
- data/lib/reek/module_context.rb +0 -49
- data/lib/reek/name.rb +0 -57
- data/lib/reek/object_refs.rb +0 -49
- data/lib/reek/reek_command.rb +0 -28
- data/lib/reek/sexp_formatter.rb +0 -10
- data/lib/reek/singleton_method_context.rb +0 -26
- data/lib/reek/smells/uncommunicative_name.rb +0 -84
- data/lib/reek/sniffer.rb +0 -177
- data/lib/reek/stop_context.rb +0 -35
- data/lib/reek/tree_dresser.rb +0 -82
- data/lib/reek/version_command.rb +0 -14
- data/lib/reek/yield_call_context.rb +0 -12
- data/spec/reek/adapters/report_spec.rb +0 -31
- data/spec/reek/adapters/should_reek_of_spec.rb +0 -138
- data/spec/reek/adapters/should_reek_only_of_spec.rb +0 -87
- data/spec/reek/block_context_spec.rb +0 -65
- data/spec/reek/class_context_spec.rb +0 -161
- data/spec/reek/code_context_spec.rb +0 -182
- data/spec/reek/configuration_spec.rb +0 -12
- data/spec/reek/if_context_spec.rb +0 -17
- data/spec/reek/masking_collection_spec.rb +0 -236
- data/spec/reek/module_context_spec.rb +0 -46
- data/spec/reek/name_spec.rb +0 -37
- data/spec/reek/object_source_spec.rb +0 -23
- data/spec/reek/reek_command_spec.rb +0 -45
- data/spec/reek/singleton_method_context_spec.rb +0 -16
- data/spec/reek/smells/smell_detector_spec.rb +0 -36
- data/spec/reek/smells/uncommunicative_name_spec.rb +0 -146
- data/spec/reek/sniffer_spec.rb +0 -11
- data/spec/reek/stop_context_spec.rb +0 -33
- data/spec/reek/tree_dresser_spec.rb +0 -20
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
@yaml
|
|
2
|
+
Feature: Report smells using simple YAML layout
|
|
3
|
+
In order to parse reek's output simply and consistently, simply
|
|
4
|
+
output a list of smells in Yaml.
|
|
5
|
+
|
|
6
|
+
Scenario: output is empty when there are no smells
|
|
7
|
+
When I run reek --yaml spec/samples/three_clean_files
|
|
8
|
+
Then it succeeds
|
|
9
|
+
And stdout equals ""
|
|
10
|
+
|
|
11
|
+
@masking
|
|
12
|
+
Scenario: masked smells always appear
|
|
13
|
+
When I run reek --yaml spec/samples/masked/dirty.rb
|
|
14
|
+
Then the exit status indicates smells
|
|
15
|
+
And it reports:
|
|
16
|
+
"""
|
|
17
|
+
---
|
|
18
|
+
- !ruby/object:Reek::SmellWarning
|
|
19
|
+
location:
|
|
20
|
+
lines:
|
|
21
|
+
- 4
|
|
22
|
+
- 6
|
|
23
|
+
context: Dirty#a
|
|
24
|
+
source: spec/samples/masked/dirty.rb
|
|
25
|
+
smell:
|
|
26
|
+
class: Duplication
|
|
27
|
+
occurrences: 2
|
|
28
|
+
subclass: DuplicateMethodCall
|
|
29
|
+
call: "@s.title"
|
|
30
|
+
message: calls @s.title twice
|
|
31
|
+
status:
|
|
32
|
+
is_active: true
|
|
33
|
+
- !ruby/object:Reek::SmellWarning
|
|
34
|
+
location:
|
|
35
|
+
lines:
|
|
36
|
+
- 5
|
|
37
|
+
- 7
|
|
38
|
+
context: Dirty#a
|
|
39
|
+
source: spec/samples/masked/dirty.rb
|
|
40
|
+
smell:
|
|
41
|
+
class: Duplication
|
|
42
|
+
occurrences: 2
|
|
43
|
+
subclass: DuplicateMethodCall
|
|
44
|
+
call: puts(@s.title)
|
|
45
|
+
message: calls puts(@s.title) twice
|
|
46
|
+
status:
|
|
47
|
+
is_active: true
|
|
48
|
+
- !ruby/object:Reek::SmellWarning
|
|
49
|
+
location:
|
|
50
|
+
lines:
|
|
51
|
+
- 5
|
|
52
|
+
context: Dirty#a
|
|
53
|
+
source: spec/samples/masked/dirty.rb
|
|
54
|
+
smell:
|
|
55
|
+
class: NestedIterators
|
|
56
|
+
depth: 2
|
|
57
|
+
subclass: NestedIterators
|
|
58
|
+
message: contains iterators nested 2 deep
|
|
59
|
+
status:
|
|
60
|
+
is_active: true
|
|
61
|
+
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
@stdin
|
|
65
|
+
Scenario: return non-zero status when there are smells
|
|
66
|
+
When I pass "class Turn; end" to reek --yaml
|
|
67
|
+
Then the exit status indicates smells
|
|
68
|
+
And it reports:
|
|
69
|
+
"""
|
|
70
|
+
---
|
|
71
|
+
- !ruby/object:Reek::SmellWarning
|
|
72
|
+
location:
|
|
73
|
+
lines:
|
|
74
|
+
- 1
|
|
75
|
+
context: Turn
|
|
76
|
+
source: $stdin
|
|
77
|
+
smell:
|
|
78
|
+
class: IrresponsibleModule
|
|
79
|
+
subclass: IrresponsibleModule
|
|
80
|
+
module_name: Turn
|
|
81
|
+
message: has no descriptive comment
|
|
82
|
+
status:
|
|
83
|
+
is_active: true
|
|
84
|
+
|
|
85
|
+
"""
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'command_line')
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Cli
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Represents an instance of a Reek application.
|
|
8
|
+
# This is the entry point for all invocations of Reek from the
|
|
9
|
+
# command line.
|
|
10
|
+
#
|
|
11
|
+
class Application
|
|
12
|
+
|
|
13
|
+
STATUS_SUCCESS = 0
|
|
14
|
+
STATUS_ERROR = 1
|
|
15
|
+
STATUS_SMELLS = 2
|
|
16
|
+
|
|
17
|
+
def initialize(argv)
|
|
18
|
+
@options = Options.new(argv)
|
|
19
|
+
@status = STATUS_SUCCESS
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def execute
|
|
23
|
+
begin
|
|
24
|
+
cmd = @options.parse
|
|
25
|
+
cmd.execute(self)
|
|
26
|
+
rescue Exception => error
|
|
27
|
+
$stderr.puts "Error: #{error}"
|
|
28
|
+
@status = STATUS_ERROR
|
|
29
|
+
end
|
|
30
|
+
return @status
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def output(text)
|
|
34
|
+
print text
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def report_success
|
|
38
|
+
@status = STATUS_SUCCESS
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def report_smells
|
|
42
|
+
@status = STATUS_SMELLS
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
require 'optparse'
|
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'report')
|
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'reek_command')
|
|
4
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'help_command')
|
|
5
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'version_command')
|
|
6
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'yaml_command')
|
|
7
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source')
|
|
8
|
+
|
|
9
|
+
module Reek
|
|
10
|
+
module Cli
|
|
11
|
+
|
|
12
|
+
#
|
|
13
|
+
# Parses the command line
|
|
14
|
+
#
|
|
15
|
+
class Options
|
|
16
|
+
|
|
17
|
+
def initialize(argv)
|
|
18
|
+
@argv = argv
|
|
19
|
+
@parser = OptionParser.new
|
|
20
|
+
@report_class = VerboseReport
|
|
21
|
+
@command_class = ReekCommand
|
|
22
|
+
@config_files = []
|
|
23
|
+
set_options
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def banner
|
|
27
|
+
progname = @parser.program_name
|
|
28
|
+
# SMELL:
|
|
29
|
+
# The following banner isn't really correct. Help, Version and Reek
|
|
30
|
+
# are really sub-commands (in the git/svn sense) and so the usage
|
|
31
|
+
# banner should show three different command-lines. The other
|
|
32
|
+
# options are all flags for the Reek sub-command.
|
|
33
|
+
#
|
|
34
|
+
# reek -h|--help Display a help message
|
|
35
|
+
#
|
|
36
|
+
# reek -v|--version Output the tool's version number
|
|
37
|
+
#
|
|
38
|
+
# reek [options] files List the smells in the given files
|
|
39
|
+
# -c|--config file Specify file(s) with config options
|
|
40
|
+
# -q|-[no-]quiet Only list files that have smells
|
|
41
|
+
# files Names of files or dirs to be checked
|
|
42
|
+
#
|
|
43
|
+
return <<EOB
|
|
44
|
+
Usage: #{progname} [options] [files]
|
|
45
|
+
|
|
46
|
+
Examples:
|
|
47
|
+
|
|
48
|
+
#{progname} lib/*.rb
|
|
49
|
+
#{progname} -q lib
|
|
50
|
+
cat my_class.rb | #{progname}
|
|
51
|
+
|
|
52
|
+
See http://wiki.github.com/kevinrutherford/reek for detailed help.
|
|
53
|
+
|
|
54
|
+
EOB
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def set_options
|
|
58
|
+
@parser.banner = banner
|
|
59
|
+
@parser.separator "Common options:"
|
|
60
|
+
@parser.on("-h", "--help", "Show this message") do
|
|
61
|
+
@command_class = HelpCommand
|
|
62
|
+
end
|
|
63
|
+
@parser.on("-v", "--version", "Show version") do
|
|
64
|
+
@command_class = VersionCommand
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
@parser.separator "\nConfiguration:"
|
|
68
|
+
@parser.on("-c", "--config FILE", "Read configuration options from FILE") do |file|
|
|
69
|
+
@config_files << file
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
@parser.separator "\nReport formatting:"
|
|
73
|
+
@parser.on("-q", "--[no-]quiet", "Suppress headings for smell-free source files") do |opt|
|
|
74
|
+
@report_class = opt ? QuietReport : VerboseReport
|
|
75
|
+
end
|
|
76
|
+
@parser.on("-y", "--yaml", "Report smells in YAML format") do
|
|
77
|
+
@command_class = YamlCommand
|
|
78
|
+
# SMELL: the args passed to the command should be tested, because it may
|
|
79
|
+
# turn out that they are passed too soon, ie. before the files have been
|
|
80
|
+
# separated out from the options
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def parse
|
|
85
|
+
@parser.parse!(@argv)
|
|
86
|
+
if @command_class == HelpCommand
|
|
87
|
+
HelpCommand.new(@parser)
|
|
88
|
+
elsif @command_class == VersionCommand
|
|
89
|
+
VersionCommand.new(@parser.program_name)
|
|
90
|
+
elsif @command_class == YamlCommand
|
|
91
|
+
sources = get_sources
|
|
92
|
+
YamlCommand.create(sources, @config_files)
|
|
93
|
+
else
|
|
94
|
+
sources = get_sources
|
|
95
|
+
ReekCommand.create(sources, @report_class, @config_files)
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def get_sources
|
|
100
|
+
if @argv.empty?
|
|
101
|
+
return [$stdin.to_reek_source('$stdin')]
|
|
102
|
+
else
|
|
103
|
+
return Source::SourceLocator.new(@argv).all_sources
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
|
|
2
|
+
module Reek
|
|
3
|
+
module Cli
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# A command to display usage information for this application.
|
|
7
|
+
#
|
|
8
|
+
class HelpCommand
|
|
9
|
+
def initialize(parser)
|
|
10
|
+
@parser = parser
|
|
11
|
+
end
|
|
12
|
+
def execute(view)
|
|
13
|
+
view.output(@parser.to_s)
|
|
14
|
+
view.report_success
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'examiner')
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Cli
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# A command to collect smells from a set of sources and write them out in
|
|
8
|
+
# text report format.
|
|
9
|
+
#
|
|
10
|
+
class ReekCommand
|
|
11
|
+
def self.create(sources, report_class, config_files = [])
|
|
12
|
+
new(report_class, sources, config_files)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def initialize(report_class, sources, config_files = [])
|
|
16
|
+
@sources = sources
|
|
17
|
+
@report_class = report_class
|
|
18
|
+
@config_files = config_files
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def execute(view)
|
|
22
|
+
had_smells = false
|
|
23
|
+
@sources.each do |source|
|
|
24
|
+
examiner = Examiner.new(source, @config_files)
|
|
25
|
+
rpt = @report_class.new(examiner)
|
|
26
|
+
had_smells ||= examiner.smelly?
|
|
27
|
+
view.output(rpt.report)
|
|
28
|
+
end
|
|
29
|
+
if had_smells
|
|
30
|
+
view.report_smells
|
|
31
|
+
else
|
|
32
|
+
view.report_success
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
module Reek
|
|
2
|
+
module Cli
|
|
3
|
+
|
|
4
|
+
module ReportFormatter
|
|
5
|
+
def header(desc, count)
|
|
6
|
+
result = "#{desc} -- #{count} warning"
|
|
7
|
+
result += 's' unless count == 1
|
|
8
|
+
result
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def format_list(warnings)
|
|
12
|
+
warnings.map do |warning|
|
|
13
|
+
" #{warning.context} #{warning.message} (#{warning.smell_class})"
|
|
14
|
+
end.join("\n")
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
module_function :format_list
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
#
|
|
21
|
+
# A report that lists every source, including those that have no smells.
|
|
22
|
+
#
|
|
23
|
+
class VerboseReport
|
|
24
|
+
|
|
25
|
+
include ReportFormatter
|
|
26
|
+
|
|
27
|
+
def initialize(examiner)
|
|
28
|
+
@examiner = examiner
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def report
|
|
32
|
+
warnings = @examiner.smells
|
|
33
|
+
warning_count = warnings.length
|
|
34
|
+
result = header(@examiner.description, warning_count)
|
|
35
|
+
result += ":\n#{format_list(warnings)}" if warning_count > 0
|
|
36
|
+
result + "\n"
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
#
|
|
41
|
+
# A report that lists a section for each source that has smells.
|
|
42
|
+
#
|
|
43
|
+
class QuietReport
|
|
44
|
+
|
|
45
|
+
include ReportFormatter
|
|
46
|
+
|
|
47
|
+
def initialize(examiner)
|
|
48
|
+
@warnings = examiner.smells
|
|
49
|
+
@smell_count = @warnings.length
|
|
50
|
+
@desc = examiner.description
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def report
|
|
54
|
+
@smell_count > 0 ? "#{header(@desc, @smell_count)}:\n#{format_list(@warnings)}\n" : ''
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
require File.join(File.dirname(File.dirname(File.dirname(File.expand_path(__FILE__)))), 'reek')
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Cli
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# A command to report the application's current version number.
|
|
8
|
+
#
|
|
9
|
+
class VersionCommand
|
|
10
|
+
def initialize(progname)
|
|
11
|
+
@progname = progname
|
|
12
|
+
end
|
|
13
|
+
def execute(view)
|
|
14
|
+
view.output("#{@progname} #{Reek::VERSION}\n")
|
|
15
|
+
view.report_success
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'examiner')
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Cli
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# A command to collect smells from a set of sources and write them out in
|
|
8
|
+
# YAML format.
|
|
9
|
+
#
|
|
10
|
+
class YamlCommand
|
|
11
|
+
def self.create(sources, config_files)
|
|
12
|
+
examiners = sources.map {|src| Examiner.new(src, config_files) }
|
|
13
|
+
new(examiners)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def initialize(examiners)
|
|
17
|
+
@examiners = examiners
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def execute(view)
|
|
21
|
+
smells = []
|
|
22
|
+
@examiners.each {|examiner| smells += examiner.smells}
|
|
23
|
+
if smells.empty?
|
|
24
|
+
view.report_success
|
|
25
|
+
else
|
|
26
|
+
view.output(smells.to_yaml)
|
|
27
|
+
view.report_smells
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
|
|
2
|
+
module Reek
|
|
3
|
+
module Core
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Superclass for all types of source code context. Each instance represents
|
|
7
|
+
# a code element of some kind, and each provides behaviour relevant to that
|
|
8
|
+
# code element. CodeContexts form a tree in the same way the code does,
|
|
9
|
+
# with each context holding a reference to a unique outer context.
|
|
10
|
+
#
|
|
11
|
+
class CodeContext
|
|
12
|
+
|
|
13
|
+
attr_reader :exp, :config
|
|
14
|
+
|
|
15
|
+
def initialize(outer, exp)
|
|
16
|
+
@outer = outer
|
|
17
|
+
@exp = exp
|
|
18
|
+
@config = local_config
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def name
|
|
22
|
+
@exp.name
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def local_nodes(type, &blk)
|
|
26
|
+
each_node(type, [:class, :module], &blk)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def each_node(type, ignoring, &blk)
|
|
30
|
+
@exp.each_node(type, ignoring, &blk)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def matches?(candidates)
|
|
34
|
+
my_fq_name = full_name
|
|
35
|
+
candidates.any? {|str| /#{str}/ === my_fq_name }
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
#
|
|
39
|
+
# Bounces messages up the context tree to the first enclosing context
|
|
40
|
+
# that knows how to deal with the request.
|
|
41
|
+
#
|
|
42
|
+
def method_missing(method, *args)
|
|
43
|
+
@outer.send(method, *args)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def num_methods
|
|
47
|
+
0
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def full_name
|
|
51
|
+
outer = @outer ? @outer.full_name : ''
|
|
52
|
+
exp.full_name(outer)
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def local_config
|
|
56
|
+
return Hash.new if @exp.nil?
|
|
57
|
+
config = Source::CodeComment.new(@exp.comments || '').config
|
|
58
|
+
return config unless @outer
|
|
59
|
+
@outer.config.deep_copy.adopt!(config)
|
|
60
|
+
# no tests for this -----^
|
|
61
|
+
end
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
require 'sexp'
|
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'method_context')
|
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'module_context')
|
|
4
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'stop_context')
|
|
5
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'singleton_method_context')
|
|
6
|
+
|
|
7
|
+
module Reek
|
|
8
|
+
module Core
|
|
9
|
+
|
|
10
|
+
#
|
|
11
|
+
# Traverses a Sexp abstract syntax tree and fires events whenever
|
|
12
|
+
# it encounters specific node types.
|
|
13
|
+
#
|
|
14
|
+
class CodeParser
|
|
15
|
+
def initialize(sniffer, ctx = StopContext.new)
|
|
16
|
+
@sniffer = sniffer
|
|
17
|
+
@element = ctx
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def process(exp)
|
|
21
|
+
meth = "process_#{exp[0]}"
|
|
22
|
+
meth = :process_default unless self.respond_to?(meth)
|
|
23
|
+
self.send(meth, exp)
|
|
24
|
+
@element
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def process_default(exp)
|
|
28
|
+
exp.each { |sub| process(sub) if Array === sub }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def process_module(exp)
|
|
32
|
+
name = Source::SexpFormatter.format(exp[1])
|
|
33
|
+
scope = ModuleContext.new(@element, name, exp)
|
|
34
|
+
push(scope) do
|
|
35
|
+
process_default(exp) unless exp.superclass == [:const, :Struct]
|
|
36
|
+
check_smells(exp[0])
|
|
37
|
+
end
|
|
38
|
+
scope
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def process_class(exp)
|
|
42
|
+
process_module(exp)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def process_defn(exp)
|
|
46
|
+
handle_context(MethodContext, exp[0], exp)
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def process_defs(exp)
|
|
50
|
+
handle_context(SingletonMethodContext, exp[0], exp)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def process_args(exp) end
|
|
54
|
+
|
|
55
|
+
def process_zsuper(exp)
|
|
56
|
+
@element.record_use_of_self
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def process_block(exp)
|
|
60
|
+
@element.count_statements(CodeParser.count_statements(exp))
|
|
61
|
+
process_default(exp)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def process_call(exp)
|
|
65
|
+
@element.record_call_to(exp)
|
|
66
|
+
process_default(exp)
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def process_attrasgn(exp)
|
|
70
|
+
process_call(exp)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def process_op_asgn1(exp)
|
|
74
|
+
process_call(exp)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def process_if(exp)
|
|
78
|
+
count_clause(exp[2])
|
|
79
|
+
count_clause(exp[3])
|
|
80
|
+
process_default(exp)
|
|
81
|
+
@element.count_statements(-1)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def process_while(exp)
|
|
85
|
+
process_until(exp)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
def process_until(exp)
|
|
89
|
+
count_clause(exp[2])
|
|
90
|
+
process_case(exp)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def process_for(exp)
|
|
94
|
+
count_clause(exp[3])
|
|
95
|
+
process_case(exp)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def process_rescue(exp)
|
|
99
|
+
count_clause(exp[1])
|
|
100
|
+
process_case(exp)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def process_resbody(exp)
|
|
104
|
+
process_when(exp)
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
def process_case(exp)
|
|
108
|
+
process_default(exp)
|
|
109
|
+
@element.count_statements(-1)
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def process_when(exp)
|
|
113
|
+
count_clause(exp[2])
|
|
114
|
+
process_default(exp)
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def process_ivar(exp)
|
|
118
|
+
process_iasgn(exp)
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def process_iasgn(exp)
|
|
122
|
+
@element.record_use_of_self
|
|
123
|
+
process_default(exp)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def process_self(exp)
|
|
127
|
+
@element.record_use_of_self
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def count_clause(sexp)
|
|
131
|
+
if sexp and !sexp.has_type?(:block)
|
|
132
|
+
@element.count_statements(1)
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def self.count_statements(exp)
|
|
137
|
+
stmts = exp[1..-1]
|
|
138
|
+
ignore = 0
|
|
139
|
+
ignore += 1 if stmts[1] == s(:nil)
|
|
140
|
+
stmts.length - ignore
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
private
|
|
144
|
+
|
|
145
|
+
def handle_context(klass, type, exp)
|
|
146
|
+
scope = klass.new(@element, exp)
|
|
147
|
+
push(scope) do
|
|
148
|
+
process_default(exp)
|
|
149
|
+
check_smells(type)
|
|
150
|
+
end
|
|
151
|
+
scope
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
def check_smells(type)
|
|
155
|
+
@sniffer.examine(@element, type)
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def push(context)
|
|
159
|
+
orig = @element
|
|
160
|
+
@element = context
|
|
161
|
+
yield
|
|
162
|
+
@element = orig
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
166
|
+
end
|