simplabs-excellent 1.0.1 → 1.2.1
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/History.txt +19 -0
- data/README.rdoc +34 -0
- data/VERSION.yml +1 -1
- data/bin/excellent +21 -6
- data/lib/simplabs/excellent.rb +7 -5
- data/lib/simplabs/excellent/checks.rb +18 -1
- data/lib/simplabs/excellent/checks/abc_metric_method_check.rb +19 -56
- data/lib/simplabs/excellent/checks/assignment_in_conditional_check.rb +16 -16
- data/lib/simplabs/excellent/checks/base.rb +30 -21
- data/lib/simplabs/excellent/checks/case_missing_else_check.rb +13 -5
- data/lib/simplabs/excellent/checks/class_line_count_check.rb +10 -8
- data/lib/simplabs/excellent/checks/class_name_check.rb +11 -10
- data/lib/simplabs/excellent/checks/control_coupling_check.rb +13 -10
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_block_check.rb +25 -9
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_check.rb +4 -20
- data/lib/simplabs/excellent/checks/cyclomatic_complexity_method_check.rb +25 -10
- data/lib/simplabs/excellent/checks/duplication_check.rb +50 -0
- data/lib/simplabs/excellent/checks/empty_rescue_body_check.rb +10 -18
- data/lib/simplabs/excellent/checks/flog_block_check.rb +40 -0
- data/lib/simplabs/excellent/checks/flog_check.rb +27 -0
- data/lib/simplabs/excellent/checks/flog_class_check.rb +40 -0
- data/lib/simplabs/excellent/checks/flog_method_check.rb +40 -0
- data/lib/simplabs/excellent/checks/for_loop_check.rb +20 -4
- data/lib/simplabs/excellent/checks/line_count_check.rb +3 -21
- data/lib/simplabs/excellent/checks/method_line_count_check.rb +9 -7
- data/lib/simplabs/excellent/checks/method_name_check.rb +13 -9
- data/lib/simplabs/excellent/checks/module_line_count_check.rb +9 -7
- data/lib/simplabs/excellent/checks/module_name_check.rb +11 -7
- data/lib/simplabs/excellent/checks/name_check.rb +3 -8
- data/lib/simplabs/excellent/checks/nested_iterators_check.rb +33 -0
- data/lib/simplabs/excellent/checks/parameter_number_check.rb +13 -12
- data/lib/simplabs/excellent/checks/rails.rb +17 -0
- data/lib/simplabs/excellent/checks/rails/attr_accessible_check.rb +38 -0
- data/lib/simplabs/excellent/checks/rails/attr_protected_check.rb +39 -0
- data/lib/simplabs/excellent/checks/singleton_variable_check.rb +32 -0
- data/lib/simplabs/excellent/extensions/sexp.rb +21 -0
- data/lib/simplabs/excellent/extensions/string.rb +23 -0
- data/lib/simplabs/excellent/parsing.rb +12 -0
- data/lib/simplabs/excellent/parsing/abc_measure.rb +52 -0
- data/lib/simplabs/excellent/parsing/block_context.rb +43 -0
- data/lib/simplabs/excellent/parsing/call_context.rb +36 -0
- data/lib/simplabs/excellent/parsing/case_context.rb +31 -0
- data/lib/simplabs/excellent/parsing/class_context.rb +68 -0
- data/lib/simplabs/excellent/parsing/code_processor.rb +154 -0
- data/lib/simplabs/excellent/parsing/conditional_context.rb +25 -0
- data/lib/simplabs/excellent/parsing/cvar_context.rb +28 -0
- data/lib/simplabs/excellent/parsing/cyclomatic_complexity_measure.rb +73 -0
- data/lib/simplabs/excellent/parsing/flog_measure.rb +192 -0
- data/lib/simplabs/excellent/parsing/for_loop_context.rb +15 -0
- data/lib/simplabs/excellent/parsing/if_context.rb +38 -0
- data/lib/simplabs/excellent/parsing/method_context.rb +50 -0
- data/lib/simplabs/excellent/parsing/module_context.rb +29 -0
- data/lib/simplabs/excellent/{core → parsing}/parser.rb +4 -2
- data/lib/simplabs/excellent/parsing/resbody_context.rb +39 -0
- data/lib/simplabs/excellent/parsing/scopeable.rb +34 -0
- data/lib/simplabs/excellent/parsing/sexp_context.rb +125 -0
- data/lib/simplabs/excellent/parsing/singleton_method_context.rb +55 -0
- data/lib/simplabs/excellent/parsing/until_context.rb +24 -0
- data/lib/simplabs/excellent/parsing/while_context.rb +24 -0
- data/lib/simplabs/excellent/runner.rb +105 -0
- data/lib/simplabs/excellent/warning.rb +53 -0
- data/spec/checks/abc_metric_method_check_spec.rb +36 -8
- data/spec/checks/assignment_in_conditional_check_spec.rb +31 -14
- data/spec/checks/case_missing_else_check_spec.rb +8 -8
- data/spec/checks/class_line_count_check_spec.rb +24 -11
- data/spec/checks/class_name_check_spec.rb +9 -9
- data/spec/checks/control_coupling_check_spec.rb +84 -13
- data/spec/checks/cyclomatic_complexity_block_check_spec.rb +13 -17
- data/spec/checks/cyclomatic_complexity_method_check_spec.rb +32 -6
- data/spec/checks/duplication_check_spec.rb +139 -0
- data/spec/checks/empty_rescue_body_check_spec.rb +54 -16
- data/spec/checks/flog_block_check_spec.rb +28 -0
- data/spec/checks/flog_class_check_spec.rb +28 -0
- data/spec/checks/flog_method_check_spec.rb +46 -0
- data/spec/checks/for_loop_check_spec.rb +11 -11
- data/spec/checks/method_line_count_check_spec.rb +11 -12
- data/spec/checks/method_name_check_spec.rb +34 -13
- data/spec/checks/module_line_count_check_spec.rb +11 -12
- data/spec/checks/module_name_check_spec.rb +31 -7
- data/spec/checks/nested_iterators_check_spec.rb +44 -0
- data/spec/checks/parameter_number_check_spec.rb +48 -12
- data/spec/checks/rails/attr_accessible_check_spec.rb +79 -0
- data/spec/checks/rails/attr_protected_check_spec.rb +77 -0
- data/spec/checks/singleton_variable_check_spec.rb +66 -0
- data/spec/{core/extensions/underscore_spec.rb → extensions/string_spec.rb} +1 -1
- metadata +58 -15
- data/README.markdown +0 -30
- data/lib/simplabs/excellent/checks/class_variable_check.rb +0 -25
- data/lib/simplabs/excellent/core.rb +0 -2
- data/lib/simplabs/excellent/core/checking_visitor.rb +0 -34
- data/lib/simplabs/excellent/core/error.rb +0 -31
- data/lib/simplabs/excellent/core/extensions/underscore.rb +0 -27
- data/lib/simplabs/excellent/core/iterator_visitor.rb +0 -29
- data/lib/simplabs/excellent/core/parse_tree_runner.rb +0 -88
- data/lib/simplabs/excellent/core/visitable_sexp.rb +0 -31
- data/spec/checks/class_variable_check_spec.rb +0 -26
@@ -6,7 +6,7 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
-
class LineCountCheck < Base
|
9
|
+
class LineCountCheck < Base #:nodoc:
|
10
10
|
|
11
11
|
def initialize(interesting_nodes, threshold)
|
12
12
|
super()
|
@@ -14,28 +14,10 @@ module Simplabs
|
|
14
14
|
@threshold = threshold
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
@
|
17
|
+
def evaluate(context)
|
18
|
+
add_warning(*warning_args(context)) unless context.line_count <= @threshold
|
19
19
|
end
|
20
20
|
|
21
|
-
def evaluate(node)
|
22
|
-
line_count = count_lines(node_to_count(node)) - 1
|
23
|
-
add_error(*error_args(node, line_count)) unless line_count <= @threshold
|
24
|
-
end
|
25
|
-
|
26
|
-
protected
|
27
|
-
|
28
|
-
def node_to_count(node)
|
29
|
-
node
|
30
|
-
end
|
31
|
-
|
32
|
-
def count_lines(node, line_numbers = [])
|
33
|
-
count = 0
|
34
|
-
line_numbers << node.line
|
35
|
-
node.children.each { |child| count += count_lines(child, line_numbers) }
|
36
|
-
line_numbers.uniq.length
|
37
|
-
end
|
38
|
-
|
39
21
|
end
|
40
22
|
|
41
23
|
end
|
@@ -6,23 +6,25 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
+
# This check reports methods which have more lines than the threshold. Methods with a large number of lines are hard to read and understand
|
10
|
+
# and often an indicator for badly designed code as well.
|
11
|
+
#
|
12
|
+
# ==== Applies to
|
13
|
+
#
|
14
|
+
# * methods
|
9
15
|
class MethodLineCountCheck < LineCountCheck
|
10
16
|
|
11
17
|
DEFAULT_THRESHOLD = 20
|
12
18
|
|
13
|
-
def initialize(options = {})
|
19
|
+
def initialize(options = {}) #:nodoc:
|
14
20
|
threshold = options[:threshold] || DEFAULT_THRESHOLD
|
15
21
|
super([:defn], threshold)
|
16
22
|
end
|
17
23
|
|
18
24
|
protected
|
19
25
|
|
20
|
-
def
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def error_args(node, line_count)
|
25
|
-
['Method {{method}} has {{count}} lines.', { :method => node[1], :count => line_count }]
|
26
|
+
def warning_args(context) #:nodoc:
|
27
|
+
[context, '{{method}} has {{count}} lines.', { :method => context.full_name, :count => context.line_count }]
|
26
28
|
end
|
27
29
|
|
28
30
|
end
|
@@ -6,23 +6,27 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
+
# This check reports methods with bad names. Badly named methods make reading and understanding the code much harder. Method names regarded as bad
|
10
|
+
# are for example:
|
11
|
+
#
|
12
|
+
# * names that are camel cased
|
13
|
+
#
|
14
|
+
# ==== Applies to
|
15
|
+
#
|
16
|
+
# * methods
|
9
17
|
class MethodNameCheck < NameCheck
|
10
18
|
|
11
|
-
DEFAULT_PATTERN = /^[_a-z<>=\[
|
19
|
+
DEFAULT_PATTERN = /^[_a-z<>=\[|+-\/\*\~\%\&`\|\^]+[_a-z0-9_<>=~@\[\]]*[=!\?]?$/
|
12
20
|
|
13
|
-
def initialize(options = {})
|
21
|
+
def initialize(options = {}) #:nodoc:
|
14
22
|
pattern = options['pattern'] || DEFAULT_PATTERN
|
15
|
-
super([:defn], pattern)
|
16
|
-
end
|
17
|
-
|
18
|
-
def find_name(node)
|
19
|
-
node[1]
|
23
|
+
super([:defn, :defs], pattern)
|
20
24
|
end
|
21
25
|
|
22
26
|
protected
|
23
27
|
|
24
|
-
def
|
25
|
-
['Bad method name {{method}}.', { :method =>
|
28
|
+
def warning_args(context) #:nodoc:
|
29
|
+
[context, 'Bad method name {{method}}.', { :method => context.full_name }]
|
26
30
|
end
|
27
31
|
|
28
32
|
end
|
@@ -6,23 +6,25 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
+
# This check reports modules which have more lines than the threshold. Modules with a large number of lines are hard to read and understand
|
10
|
+
# and often an indicator for badly designed code as well.
|
11
|
+
#
|
12
|
+
# ==== Applies to
|
13
|
+
#
|
14
|
+
# * modules
|
9
15
|
class ModuleLineCountCheck < LineCountCheck
|
10
16
|
|
11
17
|
DEFAULT_THRESHOLD = 300
|
12
18
|
|
13
|
-
def initialize(options = {})
|
19
|
+
def initialize(options = {}) #:nodoc:
|
14
20
|
threshold = options[:threshold] || DEFAULT_THRESHOLD
|
15
21
|
super([:module], threshold)
|
16
22
|
end
|
17
23
|
|
18
24
|
protected
|
19
25
|
|
20
|
-
def
|
21
|
-
|
22
|
-
end
|
23
|
-
|
24
|
-
def error_args(node, line_count)
|
25
|
-
['Module {{module}} has {{count}} lines.', { :module => node[1], :count => line_count }]
|
26
|
+
def warning_args(context) #:nodoc:
|
27
|
+
[context, '{{module}} has {{count}} lines.', { :module => context.full_name, :count => context.line_count }]
|
26
28
|
end
|
27
29
|
|
28
30
|
end
|
@@ -6,23 +6,27 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
+
# This check reports modules with bad names. Badly named modules make reading and understanding the code much harder. Module names regarded as bad
|
10
|
+
# are for example:
|
11
|
+
#
|
12
|
+
# * names that are not Pascal cased (camel cased, starting with an upper case letter)
|
13
|
+
#
|
14
|
+
# ==== Applies to
|
15
|
+
#
|
16
|
+
# * modules
|
9
17
|
class ModuleNameCheck < NameCheck
|
10
18
|
|
11
19
|
DEFAULT_PATTERN = /^[A-Z][a-zA-Z0-9]*$/
|
12
20
|
|
13
|
-
def initialize(options = {})
|
21
|
+
def initialize(options = {}) #:nodoc:
|
14
22
|
pattern = options['pattern'] || DEFAULT_PATTERN
|
15
23
|
super([:module], pattern)
|
16
24
|
end
|
17
25
|
|
18
|
-
def find_name(node)
|
19
|
-
node[1].class == Symbol ? node[1] : node[1].last
|
20
|
-
end
|
21
|
-
|
22
26
|
protected
|
23
27
|
|
24
|
-
def
|
25
|
-
['Bad module name {{module}}.', { :module =>
|
28
|
+
def warning_args(context) #:nodoc:
|
29
|
+
[context, 'Bad module name {{module}}.', { :module => context.full_name }]
|
26
30
|
end
|
27
31
|
|
28
32
|
end
|
@@ -6,7 +6,7 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
-
class NameCheck < Base
|
9
|
+
class NameCheck < Base #:nodoc:
|
10
10
|
|
11
11
|
def initialize(interesting_nodes, pattern)
|
12
12
|
super()
|
@@ -14,13 +14,8 @@ module Simplabs
|
|
14
14
|
@pattern = pattern
|
15
15
|
end
|
16
16
|
|
17
|
-
def
|
18
|
-
@
|
19
|
-
end
|
20
|
-
|
21
|
-
def evaluate(node)
|
22
|
-
name = find_name(node)
|
23
|
-
add_error(*error_args(node)) unless name.to_s =~ @pattern
|
17
|
+
def evaluate(context)
|
18
|
+
add_warning(*warning_args(context)) unless context.name.to_s =~ @pattern
|
24
19
|
end
|
25
20
|
|
26
21
|
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
# This check reports nested iterators. Nested iterators lead to introduce performance issues.
|
10
|
+
#
|
11
|
+
# ==== Applies to
|
12
|
+
#
|
13
|
+
# * blocks
|
14
|
+
class NestedIteratorsCheck < Base
|
15
|
+
|
16
|
+
def initialize #:nodoc:
|
17
|
+
super
|
18
|
+
@interesting_nodes = [:iter]
|
19
|
+
end
|
20
|
+
|
21
|
+
def evaluate(context) #:nodoc:
|
22
|
+
if context.inside_block?
|
23
|
+
add_warning(context, '{{block}} inside of {{parent}}.', { :block => context.full_name, :parent => context.parent.full_name })
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
@@ -6,25 +6,26 @@ module Simplabs
|
|
6
6
|
|
7
7
|
module Checks
|
8
8
|
|
9
|
+
# This check reports method and blocks that have more parameters than the threshold. Methods with long parameter lists are harder to understand
|
10
|
+
# and often an indicator for bad design as well.
|
11
|
+
#
|
12
|
+
# ==== Applies to
|
13
|
+
#
|
14
|
+
# * methods
|
15
|
+
# * blocks
|
9
16
|
class ParameterNumberCheck < Base
|
10
17
|
|
11
18
|
DEFAULT_THRESHOLD = 3
|
12
19
|
|
13
|
-
def initialize(options = {})
|
20
|
+
def initialize(options = {}) #:nodoc:
|
14
21
|
super()
|
15
|
-
@threshold
|
22
|
+
@threshold = options[:threshold] || DEFAULT_THRESHOLD
|
23
|
+
@interesting_nodes = [:defn, :iter, :defs]
|
16
24
|
end
|
17
25
|
|
18
|
-
def
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
def evaluate(node)
|
23
|
-
method_name = node[1]
|
24
|
-
parameters = node[2][1..-1]
|
25
|
-
parameter_count = parameters.inject(0) { |count, each| count = count + (each.class == Symbol ? 1 : 0) }
|
26
|
-
unless parameter_count <= @threshold
|
27
|
-
add_error('Method {{method}} has {{parameter_count}} parameters.', { :method => method_name, :parameter_count => parameter_count })
|
26
|
+
def evaluate(context) #:nodoc:
|
27
|
+
unless context.parameters.length <= @threshold
|
28
|
+
add_warning(context, '{{method}} has {{parameters}} parameters.', { :method => context.full_name, :parameters => context.parameters.length })
|
28
29
|
end
|
29
30
|
end
|
30
31
|
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Checks
|
6
|
+
|
7
|
+
module Rails #:nodoc:
|
8
|
+
end
|
9
|
+
|
10
|
+
end
|
11
|
+
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
require 'simplabs/excellent/checks/rails/attr_accessible_check'
|
17
|
+
require 'simplabs/excellent/checks/rails/attr_protected_check'
|
@@ -0,0 +1,38 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
|
11
|
+
# This check reports +ActiveRecord+ models that do not specify +attr_accessible+. Specifying +attr_accessible+ is viable to protect models from
|
12
|
+
# mass assignment attacks (see http://guides.rubyonrails.org/security.html#mass-assignment). +attr_accessible+ specifies a list of properties
|
13
|
+
# that are writeable by mass assignments. For a +User+ model for example, that list would possibly include properties like +first_name+ and
|
14
|
+
# +last_name+ while it should not include properties like +is_admin+.
|
15
|
+
#
|
16
|
+
# ==== Applies to
|
17
|
+
#
|
18
|
+
# * +ActiveRecord+ models
|
19
|
+
class AttrAccessibleCheck < Base
|
20
|
+
|
21
|
+
def initialize #:nodoc:
|
22
|
+
super
|
23
|
+
@interesting_nodes = [:class]
|
24
|
+
end
|
25
|
+
|
26
|
+
def evaluate(context) #:nodoc:
|
27
|
+
add_warning(context, '{{class}} does not specify attr_accessible.', { :class => context.full_name }) if context.activerecord_model? && !context.specifies_attr_accessible?
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
|
34
|
+
end
|
35
|
+
|
36
|
+
end
|
37
|
+
|
38
|
+
end
|
@@ -0,0 +1,39 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
module Rails
|
10
|
+
|
11
|
+
# This check reports +ActiveRecord+ models that specify +attr_protected+. Like +attr_accessible+, +attr_protected+ is a helper to secure
|
12
|
+
# +ActiveRecord+ models against mass assignment attacks (see http://guides.rubyonrails.org/security.html#mass-assignment), but instead of
|
13
|
+
# specifying a white list of properties that are writeable by mass assignments as +attr_accessible+ does, +attr_protected+ specifies a black
|
14
|
+
# list. Such a black list approach is usually less secure since the list has to be updated for every new property that is introduced, which
|
15
|
+
# is easy to forget.
|
16
|
+
#
|
17
|
+
# ==== Applies to
|
18
|
+
#
|
19
|
+
# * +ActiveRecord+ models
|
20
|
+
class AttrProtectedCheck < Base
|
21
|
+
|
22
|
+
def initialize #:nodoc:
|
23
|
+
super
|
24
|
+
@interesting_nodes = [:class]
|
25
|
+
end
|
26
|
+
|
27
|
+
def evaluate(context) #:nodoc:
|
28
|
+
add_warning(context, '{{class}} specifies attr_protected.', { :class => context.full_name }) if context.activerecord_model? && context.specifies_attr_protected?
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
@@ -0,0 +1,32 @@
|
|
1
|
+
require 'simplabs/excellent/checks/base'
|
2
|
+
|
3
|
+
module Simplabs
|
4
|
+
|
5
|
+
module Excellent
|
6
|
+
|
7
|
+
module Checks
|
8
|
+
|
9
|
+
# This check reports class variables. Class variables in Ruby have a very complicated inheritance policy that often leads to errors. Usually class
|
10
|
+
# variables can be replaced with another construct which will also lead to better design.
|
11
|
+
#
|
12
|
+
# ==== Applies to
|
13
|
+
#
|
14
|
+
# * class variables
|
15
|
+
class SingletonVariableCheck < Base
|
16
|
+
|
17
|
+
def initialize #:nodoc:
|
18
|
+
super
|
19
|
+
@interesting_nodes = [:cvar]
|
20
|
+
end
|
21
|
+
|
22
|
+
def evaluate(context) #:nodoc:
|
23
|
+
add_warning(context, 'Singleton variable {{variable}} used.', { :variable => context.full_name })
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
|
28
|
+
end
|
29
|
+
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module Simplabs
|
2
|
+
|
3
|
+
module Excellent
|
4
|
+
|
5
|
+
module Extensions #:nodoc:
|
6
|
+
|
7
|
+
::String.class_eval do
|
8
|
+
|
9
|
+
def underscore
|
10
|
+
to_s.gsub(/::/, '/').
|
11
|
+
gsub(/([A-Z]+)([A-Z][a-z])/,'\1_\2').
|
12
|
+
gsub(/([a-z\d])([A-Z])/,'\1_\2').
|
13
|
+
tr("-", "_").
|
14
|
+
downcase
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|