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,74 @@
|
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Smells
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# An Uncommunicative Name is a name that doesn't communicate its intent
|
|
9
|
+
# well enough.
|
|
10
|
+
#
|
|
11
|
+
# Poor names make it hard for the reader to build a mental picture
|
|
12
|
+
# of what's going on in the code. They can also be mis-interpreted;
|
|
13
|
+
# and they hurt the flow of reading, because the reader must slow
|
|
14
|
+
# down to interpret the names.
|
|
15
|
+
#
|
|
16
|
+
# Currently +UncommunicativeMethodName+ checks for
|
|
17
|
+
# * 1-character names
|
|
18
|
+
# * names ending with a number
|
|
19
|
+
#
|
|
20
|
+
class UncommunicativeMethodName < SmellDetector
|
|
21
|
+
|
|
22
|
+
SMELL_CLASS = 'UncommunicativeName'
|
|
23
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
24
|
+
METHOD_NAME_KEY = 'method_name'
|
|
25
|
+
|
|
26
|
+
# The name of the config field that lists the regexps of
|
|
27
|
+
# smelly names to be reported.
|
|
28
|
+
REJECT_KEY = 'reject'
|
|
29
|
+
|
|
30
|
+
DEFAULT_REJECT_SET = [/^[a-z]$/, /[0-9]$/, /[A-Z]/]
|
|
31
|
+
|
|
32
|
+
# The name of the config field that lists the specific names that are
|
|
33
|
+
# to be treated as exceptions; these names will not be reported as
|
|
34
|
+
# uncommunicative.
|
|
35
|
+
ACCEPT_KEY = 'accept'
|
|
36
|
+
|
|
37
|
+
DEFAULT_ACCEPT_SET = []
|
|
38
|
+
|
|
39
|
+
def self.default_config
|
|
40
|
+
super.adopt(
|
|
41
|
+
REJECT_KEY => DEFAULT_REJECT_SET,
|
|
42
|
+
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.contexts # :nodoc:
|
|
47
|
+
[:defn, :defs]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(source, config = UncommunicativeMethodName.default_config)
|
|
51
|
+
super(source, config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# Checks the given +context+ for uncommunicative names.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<SmellWarning>]
|
|
58
|
+
#
|
|
59
|
+
def examine_context(ctx)
|
|
60
|
+
@reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
|
61
|
+
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
|
62
|
+
name = ctx.name
|
|
63
|
+
return [] if @accept_names.include?(ctx.full_name)
|
|
64
|
+
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
|
65
|
+
return [] if @accept_names.include?(var)
|
|
66
|
+
return [] unless @reject_names.detect {|patt| patt === var}
|
|
67
|
+
smell = SmellWarning.new('UncommunicativeName', ctx.full_name, [ctx.exp.line],
|
|
68
|
+
"has the name '#{name}'",
|
|
69
|
+
@source, 'UncommunicativeMethodName', {METHOD_NAME_KEY => name.to_s})
|
|
70
|
+
[smell]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Smells
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# An Uncommunicative Name is a name that doesn't communicate its intent
|
|
9
|
+
# well enough.
|
|
10
|
+
#
|
|
11
|
+
# Poor names make it hard for the reader to build a mental picture
|
|
12
|
+
# of what's going on in the code. They can also be mis-interpreted;
|
|
13
|
+
# and they hurt the flow of reading, because the reader must slow
|
|
14
|
+
# down to interpret the names.
|
|
15
|
+
#
|
|
16
|
+
# Currently +UncommunicativeModuleName+ checks for
|
|
17
|
+
# * 1-character names
|
|
18
|
+
# * names ending with a number
|
|
19
|
+
#
|
|
20
|
+
class UncommunicativeModuleName < SmellDetector
|
|
21
|
+
|
|
22
|
+
SMELL_CLASS = 'UncommunicativeName'
|
|
23
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
24
|
+
MODULE_NAME_KEY = 'module_name'
|
|
25
|
+
|
|
26
|
+
# The name of the config field that lists the regexps of
|
|
27
|
+
# smelly names to be reported.
|
|
28
|
+
REJECT_KEY = 'reject'
|
|
29
|
+
|
|
30
|
+
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/]
|
|
31
|
+
|
|
32
|
+
# The name of the config field that lists the specific names that are
|
|
33
|
+
# to be treated as exceptions; these names will not be reported as
|
|
34
|
+
# uncommunicative.
|
|
35
|
+
ACCEPT_KEY = 'accept'
|
|
36
|
+
|
|
37
|
+
DEFAULT_ACCEPT_SET = ['Inline::C']
|
|
38
|
+
|
|
39
|
+
def self.default_config
|
|
40
|
+
super.adopt(
|
|
41
|
+
REJECT_KEY => DEFAULT_REJECT_SET,
|
|
42
|
+
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.contexts # :nodoc:
|
|
47
|
+
[:module, :class]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(source, config = UncommunicativeModuleName.default_config)
|
|
51
|
+
super(source, config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# Checks the given +context+ for uncommunicative names.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<SmellWarning>]
|
|
58
|
+
#
|
|
59
|
+
def examine_context(ctx)
|
|
60
|
+
@reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
|
61
|
+
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
|
62
|
+
name = ctx.exp.simple_name
|
|
63
|
+
return [] if @accept_names.include?(ctx.full_name)
|
|
64
|
+
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
|
65
|
+
return [] if @accept_names.include?(var)
|
|
66
|
+
return [] unless @reject_names.detect {|patt| patt === var}
|
|
67
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
68
|
+
"has the name '#{name}'",
|
|
69
|
+
@source, SMELL_SUBCLASS, {MODULE_NAME_KEY => name.to_s})
|
|
70
|
+
[smell]
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Smells
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# An Uncommunicative Name is a name that doesn't communicate its intent
|
|
9
|
+
# well enough.
|
|
10
|
+
#
|
|
11
|
+
# Poor names make it hard for the reader to build a mental picture
|
|
12
|
+
# of what's going on in the code. They can also be mis-interpreted;
|
|
13
|
+
# and they hurt the flow of reading, because the reader must slow
|
|
14
|
+
# down to interpret the names.
|
|
15
|
+
#
|
|
16
|
+
# Currently +UncommunicativeParameterName+ checks for
|
|
17
|
+
# * 1-character names
|
|
18
|
+
# * names ending with a number
|
|
19
|
+
#
|
|
20
|
+
class UncommunicativeParameterName < SmellDetector
|
|
21
|
+
|
|
22
|
+
SMELL_CLASS = 'UncommunicativeName'
|
|
23
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
24
|
+
PARAMETER_NAME_KEY = 'parameter_name'
|
|
25
|
+
|
|
26
|
+
# The name of the config field that lists the regexps of
|
|
27
|
+
# smelly names to be reported.
|
|
28
|
+
REJECT_KEY = 'reject'
|
|
29
|
+
|
|
30
|
+
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
|
|
31
|
+
|
|
32
|
+
# The name of the config field that lists the specific names that are
|
|
33
|
+
# to be treated as exceptions; these names will not be reported as
|
|
34
|
+
# uncommunicative.
|
|
35
|
+
ACCEPT_KEY = 'accept'
|
|
36
|
+
|
|
37
|
+
DEFAULT_ACCEPT_SET = []
|
|
38
|
+
|
|
39
|
+
def self.default_config
|
|
40
|
+
super.adopt(
|
|
41
|
+
REJECT_KEY => DEFAULT_REJECT_SET,
|
|
42
|
+
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.contexts # :nodoc:
|
|
47
|
+
[:defn, :defs]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(source, config = UncommunicativeParameterName.default_config)
|
|
51
|
+
super(source, config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# Checks the given +context+ for uncommunicative names.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<SmellWarning>]
|
|
58
|
+
#
|
|
59
|
+
def examine_context(ctx)
|
|
60
|
+
@reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
|
61
|
+
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
|
62
|
+
ctx.exp.parameter_names.select do |name|
|
|
63
|
+
is_bad_name?(name, ctx)
|
|
64
|
+
end.map do |name|
|
|
65
|
+
smell = SmellWarning.new(SMELL_CLASS, ctx.full_name, [ctx.exp.line],
|
|
66
|
+
"has the parameter name '#{name}'",
|
|
67
|
+
@source, SMELL_SUBCLASS, {PARAMETER_NAME_KEY => name.to_s})
|
|
68
|
+
smell
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def is_bad_name?(name, ctx)
|
|
73
|
+
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
|
74
|
+
return false if var == '*' or @accept_names.include?(var)
|
|
75
|
+
@reject_names.detect {|patt| patt === var}
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
|
|
4
|
+
module Reek
|
|
5
|
+
module Smells
|
|
6
|
+
|
|
7
|
+
#
|
|
8
|
+
# An Uncommunicative Name is a name that doesn't communicate its intent
|
|
9
|
+
# well enough.
|
|
10
|
+
#
|
|
11
|
+
# Poor names make it hard for the reader to build a mental picture
|
|
12
|
+
# of what's going on in the code. They can also be mis-interpreted;
|
|
13
|
+
# and they hurt the flow of reading, because the reader must slow
|
|
14
|
+
# down to interpret the names.
|
|
15
|
+
#
|
|
16
|
+
# Currently +UncommunicativeName+ checks for
|
|
17
|
+
# * 1-character names
|
|
18
|
+
# * names ending with a number
|
|
19
|
+
#
|
|
20
|
+
class UncommunicativeVariableName < SmellDetector
|
|
21
|
+
|
|
22
|
+
SMELL_CLASS = 'UncommunicativeName'
|
|
23
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
24
|
+
VARIABLE_NAME_KEY = 'variable_name'
|
|
25
|
+
|
|
26
|
+
# The name of the config field that lists the regexps of
|
|
27
|
+
# smelly names to be reported.
|
|
28
|
+
REJECT_KEY = 'reject'
|
|
29
|
+
|
|
30
|
+
DEFAULT_REJECT_SET = [/^.$/, /[0-9]$/, /[A-Z]/]
|
|
31
|
+
|
|
32
|
+
# The name of the config field that lists the specific names that are
|
|
33
|
+
# to be treated as exceptions; these names will not be reported as
|
|
34
|
+
# uncommunicative.
|
|
35
|
+
ACCEPT_KEY = 'accept'
|
|
36
|
+
|
|
37
|
+
DEFAULT_ACCEPT_SET = []
|
|
38
|
+
|
|
39
|
+
def self.default_config
|
|
40
|
+
super.adopt(
|
|
41
|
+
REJECT_KEY => DEFAULT_REJECT_SET,
|
|
42
|
+
ACCEPT_KEY => DEFAULT_ACCEPT_SET
|
|
43
|
+
)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def self.contexts # :nodoc:
|
|
47
|
+
[:module, :class, :defn, :defs]
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def initialize(source, config = UncommunicativeVariableName.default_config)
|
|
51
|
+
super(source, config)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
#
|
|
55
|
+
# Checks the given +context+ for uncommunicative names.
|
|
56
|
+
#
|
|
57
|
+
# @return [Array<SmellWarning>]
|
|
58
|
+
#
|
|
59
|
+
def examine_context(ctx)
|
|
60
|
+
@reject_names = value(REJECT_KEY, ctx, DEFAULT_REJECT_SET)
|
|
61
|
+
@accept_names = value(ACCEPT_KEY, ctx, DEFAULT_ACCEPT_SET)
|
|
62
|
+
variable_names(ctx.exp).select do |name, lines|
|
|
63
|
+
is_bad_name?(name, ctx)
|
|
64
|
+
end.map do |name, lines|
|
|
65
|
+
SmellWarning.new(SMELL_CLASS, ctx.full_name, lines,
|
|
66
|
+
"has the variable name '#{name}'",
|
|
67
|
+
@source, SMELL_SUBCLASS, {VARIABLE_NAME_KEY => name.to_s})
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def is_bad_name?(name, ctx)
|
|
72
|
+
var = name.to_s.gsub(/^[@\*\&]*/, '')
|
|
73
|
+
return false if @accept_names.include?(var)
|
|
74
|
+
@reject_names.detect {|patt| patt === var}
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def variable_names(exp)
|
|
78
|
+
assignment_nodes = exp.each_node(:lasgn, [:class, :module, :defs, :defn])
|
|
79
|
+
case exp.first
|
|
80
|
+
when :class, :module
|
|
81
|
+
assignment_nodes += exp.each_node(:iasgn, [:class, :module])
|
|
82
|
+
end
|
|
83
|
+
result = Hash.new {|hash, key| hash[key] = []}
|
|
84
|
+
assignment_nodes.each {|asgn| result[asgn[1]].push(asgn.line) }
|
|
85
|
+
result
|
|
86
|
+
end
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
end
|
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
require '
|
|
2
|
-
require '
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smell_detector')
|
|
2
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'smell_warning')
|
|
3
|
+
require File.join(File.dirname(File.dirname(File.expand_path(__FILE__))), 'source', 'reference_collector')
|
|
3
4
|
|
|
4
5
|
module Reek
|
|
5
6
|
module Smells
|
|
@@ -16,8 +17,27 @@ module Reek
|
|
|
16
17
|
# * doesn't use any of self's instance variables, and
|
|
17
18
|
# * doesn't use any of self's methods
|
|
18
19
|
#
|
|
20
|
+
# A Utility Function often arises because it must manipulate
|
|
21
|
+
# other objects (usually its arguments) to get them into a
|
|
22
|
+
# useful form; one force preventing them (the
|
|
23
|
+
# arguments) doing this themselves is that the common
|
|
24
|
+
# knowledge lives outside the arguments, or the arguments
|
|
25
|
+
# are of too basic a type to justify extending that type.
|
|
26
|
+
# Therefore there must be something which 'knows' about the contents
|
|
27
|
+
# or purposes of the arguments. That thing would have to
|
|
28
|
+
# be more than just a basic type, because the basic types
|
|
29
|
+
# are either containers which don't know about their
|
|
30
|
+
# contents, or they are single objects which can't capture
|
|
31
|
+
# their relationship with their fellows of the same type.
|
|
32
|
+
# So, this thing with the extra knowledge should be
|
|
33
|
+
# reified into a class, and the utility method will most
|
|
34
|
+
# likely belong there.
|
|
35
|
+
#
|
|
19
36
|
class UtilityFunction < SmellDetector
|
|
20
37
|
|
|
38
|
+
SMELL_SUBCLASS = self.name.split(/::/)[-1]
|
|
39
|
+
SMELL_CLASS = 'LowCohesion'
|
|
40
|
+
|
|
21
41
|
# The name of the config field that sets the maximum number of
|
|
22
42
|
# calls permitted within a helper method. Any method with more than
|
|
23
43
|
# this number of method calls on other objects will be considered a
|
|
@@ -26,24 +46,43 @@ module Reek
|
|
|
26
46
|
|
|
27
47
|
DEFAULT_HELPER_CALLS_LIMIT = 1
|
|
28
48
|
|
|
29
|
-
|
|
30
|
-
|
|
49
|
+
class << self
|
|
50
|
+
def contexts # :nodoc:
|
|
51
|
+
[:defn]
|
|
52
|
+
end
|
|
53
|
+
def default_config
|
|
54
|
+
super.adopt(HELPER_CALLS_LIMIT_KEY => DEFAULT_HELPER_CALLS_LIMIT)
|
|
55
|
+
end
|
|
31
56
|
end
|
|
32
57
|
|
|
33
|
-
def initialize(config = UtilityFunction.default_config)
|
|
34
|
-
super(config)
|
|
58
|
+
def initialize(source, config = UtilityFunction.default_config)
|
|
59
|
+
super(source, config)
|
|
35
60
|
end
|
|
36
61
|
|
|
37
62
|
#
|
|
38
63
|
# Checks whether the given +method+ is a utility function.
|
|
39
|
-
# Remembers any smells found.
|
|
40
64
|
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
65
|
+
# @return [Array<SmellWarning>]
|
|
66
|
+
#
|
|
67
|
+
def examine_context(method_ctx)
|
|
68
|
+
return [] if method_ctx.num_statements == 0
|
|
69
|
+
return [] if depends_on_instance?(method_ctx.exp)
|
|
70
|
+
return [] if num_helper_methods(method_ctx) <= value(HELPER_CALLS_LIMIT_KEY, method_ctx, DEFAULT_HELPER_CALLS_LIMIT)
|
|
45
71
|
# SMELL: loads of calls to value{} with the above pattern
|
|
46
|
-
|
|
72
|
+
smell = SmellWarning.new(SMELL_CLASS, method_ctx.full_name, [method_ctx.exp.line],
|
|
73
|
+
"doesn't depend on instance state",
|
|
74
|
+
@source, SMELL_SUBCLASS)
|
|
75
|
+
[smell]
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
def depends_on_instance?(exp)
|
|
81
|
+
Reek::Source::ReferenceCollector.new(exp).num_refs_to_self > 0
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def num_helper_methods(method_ctx)
|
|
85
|
+
method_ctx.local_nodes(:call).length
|
|
47
86
|
end
|
|
48
87
|
end
|
|
49
88
|
end
|
data/lib/reek/smells.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'attribute')
|
|
2
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'boolean_parameter')
|
|
3
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'class_variable')
|
|
4
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'control_couple')
|
|
5
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'data_clump')
|
|
6
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'duplication')
|
|
7
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'feature_envy')
|
|
8
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'irresponsible_module')
|
|
9
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'large_class')
|
|
10
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'long_method')
|
|
11
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'long_parameter_list')
|
|
12
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'long_yield_list')
|
|
13
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'nested_iterators')
|
|
14
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'simulated_polymorphism')
|
|
15
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_method_name')
|
|
16
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_module_name')
|
|
17
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_parameter_name')
|
|
18
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'uncommunicative_variable_name')
|
|
19
|
+
require File.join( File.dirname( File.expand_path(__FILE__)), 'smells', 'utility_function')
|
|
20
|
+
# SMELL: Duplication -- all these should be found automagically
|
|
21
|
+
|
|
22
|
+
module Reek
|
|
23
|
+
|
|
24
|
+
#
|
|
25
|
+
# This module contains the various smell detectors.
|
|
26
|
+
#
|
|
27
|
+
module Smells
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
module Reek
|
|
3
|
+
module Source
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# A comment header from an abstract syntax tree; found directly above
|
|
7
|
+
# module, class and method definitions.
|
|
8
|
+
#
|
|
9
|
+
class CodeComment
|
|
10
|
+
CONFIG_REGEX = /:reek:(\w+)(:\s*\{.*?\})?/
|
|
11
|
+
|
|
12
|
+
def initialize(text)
|
|
13
|
+
@config = Hash.new { |hash,key| hash[key] = {} }
|
|
14
|
+
@text = text.gsub(CONFIG_REGEX) do |m|
|
|
15
|
+
add_to_config($1, $2)
|
|
16
|
+
''
|
|
17
|
+
end.gsub(/#/, '').gsub(/\n/, '').strip
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def config
|
|
21
|
+
@config
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def is_descriptive?
|
|
25
|
+
@text.split(/\s+/).length >= 2
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
protected
|
|
29
|
+
def add_to_config(smell, options)
|
|
30
|
+
options ||= ': { enabled: false }'
|
|
31
|
+
@config.merge! YAML.load(smell.gsub(/(?:^|_)(.)/) { $1.upcase } + options)
|
|
32
|
+
# extend this to all configs --------------------------^
|
|
33
|
+
# extend to allow configuration of whole smell class, not just subclass
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
require 'yaml'
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Source
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# A file called <something>.reek containing configuration settings for
|
|
8
|
+
# any or all of the smell detectors.
|
|
9
|
+
#
|
|
10
|
+
class ConfigFile
|
|
11
|
+
@@bad_config_files = []
|
|
12
|
+
|
|
13
|
+
#
|
|
14
|
+
# Load the YAML config file from the supplied +file_path+.
|
|
15
|
+
#
|
|
16
|
+
def initialize(file_path)
|
|
17
|
+
@file_path = file_path
|
|
18
|
+
@hash = load
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
#
|
|
22
|
+
# Configure the given sniffer using the contents of the config file.
|
|
23
|
+
#
|
|
24
|
+
def configure(sniffer)
|
|
25
|
+
@hash.each do |klass_name, config|
|
|
26
|
+
klass = find_class(klass_name)
|
|
27
|
+
sniffer.configure(klass, config) if klass
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
#
|
|
32
|
+
# Find the class with this name if it exsits.
|
|
33
|
+
# If not, report the problem and return +nil+.
|
|
34
|
+
#
|
|
35
|
+
def find_class(name)
|
|
36
|
+
begin
|
|
37
|
+
klass = Reek::Smells.const_get(name)
|
|
38
|
+
rescue
|
|
39
|
+
klass = nil
|
|
40
|
+
end
|
|
41
|
+
problem("\"#{name}\" is not a code smell") unless klass
|
|
42
|
+
klass
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
#
|
|
46
|
+
# Load the file path with which this was initialized,
|
|
47
|
+
# unless it is already known to be a bad configuration file.
|
|
48
|
+
# If it won't load, then it is considered a bad file.
|
|
49
|
+
#
|
|
50
|
+
def load
|
|
51
|
+
unless @@bad_config_files.include?(@file_path)
|
|
52
|
+
begin
|
|
53
|
+
return YAML.load_file(@file_path) || {}
|
|
54
|
+
rescue Exception => err
|
|
55
|
+
@@bad_config_files << @file_path
|
|
56
|
+
problem(err.to_s)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
return {}
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
#
|
|
63
|
+
# Report invalid configuration file to standard
|
|
64
|
+
# Error.
|
|
65
|
+
#
|
|
66
|
+
def problem(reason)
|
|
67
|
+
$stderr.puts "Error: Invalid configuration file \"#{File.basename(@file_path)}\" -- #{reason}"
|
|
68
|
+
# SMELL: Duplication of 'Error:'
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'source_code')
|
|
2
|
+
|
|
3
|
+
#
|
|
4
|
+
# Extensions to +File+ needed by Reek.
|
|
5
|
+
#
|
|
6
|
+
class File
|
|
7
|
+
#
|
|
8
|
+
# Creates a new +Source+ that assumes this File contains Ruby source
|
|
9
|
+
# code and prepares it to be examined for code smells.
|
|
10
|
+
#
|
|
11
|
+
# @return [Reek::Source::SourceFile]
|
|
12
|
+
#
|
|
13
|
+
def to_reek_source
|
|
14
|
+
Reek::Source::SourceFile.new(path)
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
#
|
|
19
|
+
# Extensions to +IO+ needed by Reek.
|
|
20
|
+
#
|
|
21
|
+
class IO
|
|
22
|
+
#
|
|
23
|
+
# Creates a new +Source+ that assumes this IO stream contains Ruby source
|
|
24
|
+
# code and prepares it to be examined for code smells.
|
|
25
|
+
#
|
|
26
|
+
# @return [Reek::Source::SourceCode]
|
|
27
|
+
#
|
|
28
|
+
def to_reek_source(description = 'io')
|
|
29
|
+
Reek::Source::SourceCode.new(self.readlines.join, description)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
#
|
|
34
|
+
# Extensions to +String+ needed by Reek.
|
|
35
|
+
#
|
|
36
|
+
class String
|
|
37
|
+
#
|
|
38
|
+
# Creates a new +Source+ that assumes this string contains Ruby source
|
|
39
|
+
# code and prepares it to be examined for code smells.
|
|
40
|
+
#
|
|
41
|
+
# @return [Reek::Source::SourceCode]
|
|
42
|
+
#
|
|
43
|
+
def to_reek_source
|
|
44
|
+
Reek::Source::SourceCode.new(self, 'string')
|
|
45
|
+
end
|
|
46
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
|
|
2
|
+
module Reek
|
|
3
|
+
module Source
|
|
4
|
+
|
|
5
|
+
#
|
|
6
|
+
# Locates references to the current object within a portion
|
|
7
|
+
# of an abstract syntax tree.
|
|
8
|
+
#
|
|
9
|
+
class ReferenceCollector
|
|
10
|
+
STOP_NODES = [:class, :module, :defn, :defs]
|
|
11
|
+
|
|
12
|
+
def initialize(ast)
|
|
13
|
+
@ast = ast
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def num_refs_to_self
|
|
17
|
+
result = 0
|
|
18
|
+
[:self, :zsuper, :ivar, :iasgn].each do |node_type|
|
|
19
|
+
@ast.look_for(node_type, STOP_NODES) { result += 1}
|
|
20
|
+
end
|
|
21
|
+
@ast.look_for(:call, STOP_NODES) do |call|
|
|
22
|
+
result += 1 unless call.receiver
|
|
23
|
+
end
|
|
24
|
+
result
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
require 'ruby2ruby'
|
|
2
|
+
|
|
3
|
+
module Reek
|
|
4
|
+
module Source
|
|
5
|
+
|
|
6
|
+
#
|
|
7
|
+
# Formats snippets of syntax tree back into Ruby source code.
|
|
8
|
+
#
|
|
9
|
+
class SexpFormatter
|
|
10
|
+
def self.format(sexp)
|
|
11
|
+
return sexp.to_s unless Array === sexp
|
|
12
|
+
sexp = YAML::load(YAML::dump(sexp))
|
|
13
|
+
Ruby2Ruby.new.process(sexp)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
require 'ruby_parser'
|
|
2
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'config_file')
|
|
3
|
+
require File.join(File.dirname(File.expand_path(__FILE__)), 'tree_dresser')
|
|
4
|
+
|
|
5
|
+
module Reek
|
|
6
|
+
module Source
|
|
7
|
+
|
|
8
|
+
#
|
|
9
|
+
# A +Source+ object represents a chunk of Ruby source code.
|
|
10
|
+
#
|
|
11
|
+
class SourceCode
|
|
12
|
+
|
|
13
|
+
@@err_io = $stderr
|
|
14
|
+
|
|
15
|
+
class << self
|
|
16
|
+
def err_io=(io)
|
|
17
|
+
original = @@err_io
|
|
18
|
+
@@err_io = io
|
|
19
|
+
original
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
attr_reader :desc
|
|
24
|
+
|
|
25
|
+
def initialize(code, desc, parser = RubyParser.new)
|
|
26
|
+
@source = code
|
|
27
|
+
@desc = desc
|
|
28
|
+
@parser = parser
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def configure(sniffer) end
|
|
32
|
+
|
|
33
|
+
def syntax_tree
|
|
34
|
+
begin
|
|
35
|
+
ast = @parser.parse(@source, @desc)
|
|
36
|
+
rescue Exception => error
|
|
37
|
+
@@err_io.puts "#{desc}: #{error.class.name}: #{error}"
|
|
38
|
+
end
|
|
39
|
+
ast ||= s()
|
|
40
|
+
TreeDresser.new.dress(ast)
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|