cookstyle 6.4.4 → 6.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (26) hide show
  1. checksums.yaml +4 -4
  2. data/Gemfile +1 -1
  3. data/config/cookstyle.yml +287 -12
  4. data/config/disable_all.yml +12 -0
  5. data/config/upstream.yml +82 -19
  6. data/lib/cookstyle.rb +1 -1
  7. data/lib/cookstyle/version.rb +2 -2
  8. data/lib/rubocop/chef/platform_helpers.rb +2 -1
  9. data/lib/rubocop/cop/chef/correctness/invalid_platform_family_values_in_case.rb +77 -0
  10. data/lib/rubocop/cop/chef/correctness/invalid_platform_values_in_case.rb +77 -0
  11. data/lib/rubocop/cop/chef/correctness/invalid_version_metadata.rb +1 -1
  12. data/lib/rubocop/cop/chef/correctness/lazy_eval_node_attribute_defaults.rb +56 -0
  13. data/lib/rubocop/cop/chef/correctness/openssl_password_helpers.rb +45 -0
  14. data/lib/rubocop/cop/chef/deprecation/deprecated_chefspec_platform.rb +10 -4
  15. data/lib/rubocop/cop/chef/deprecation/hwrp_without_provides.rb +141 -0
  16. data/lib/rubocop/cop/chef/deprecation/resource_uses_only_resource_name.rb +86 -0
  17. data/lib/rubocop/cop/chef/deprecation/ruby_27_keyword_argument_warnings.rb +59 -0
  18. data/lib/rubocop/cop/chef/deprecation/xml_ruby_recipe.rb +3 -3
  19. data/lib/rubocop/cop/chef/modernize/includes_mixin_shellout.rb +24 -3
  20. data/lib/rubocop/cop/chef/modernize/shell_out_helper.rb +64 -0
  21. data/lib/rubocop/cop/chef/modernize/use_multipackage_installs.rb +8 -4
  22. data/lib/rubocop/cop/chef/sharing/invalid_license_string.rb +1 -1
  23. data/lib/rubocop/monkey_patches/team.rb +24 -0
  24. metadata +13 -6
  25. data/lib/rubocop/cop/chef/deprecation/resource_without_name_or_provides.rb +0 -81
  26. data/lib/rubocop/monkey_patches/commissioner.rb +0 -26
@@ -211,10 +211,14 @@ Lint/BooleanSymbol:
211
211
  Enabled: false
212
212
  Lint/CircularArgumentReference:
213
213
  Enabled: false
214
+ Lint/ConstantResolution:
215
+ Enabled: false
214
216
  Lint/Debugger:
215
217
  Enabled: false
216
218
  Lint/DeprecatedClassMethods:
217
219
  Enabled: false
220
+ Lint/DeprecatedOpenSSLConstant:
221
+ Enabled: false
218
222
  Lint/DisjunctiveAssignmentInConstructor:
219
223
  Enabled: false
220
224
  Lint/DuplicateCaseCondition:
@@ -263,6 +267,8 @@ Lint/Loop:
263
267
  Enabled: false
264
268
  Lint/MissingCopEnableDirective:
265
269
  Enabled: false
270
+ Lint/MixedRegexpCaptureTypes:
271
+ Enabled: false
266
272
  Lint/MultipleComparison:
267
273
  Enabled: false
268
274
  Lint/NestedMethodDefinition:
@@ -561,6 +567,8 @@ Style/MethodCallWithoutArgsParentheses:
561
567
  Enabled: false
562
568
  Style/MethodCallWithArgsParentheses:
563
569
  Enabled: false
570
+ Style/RedundantFetchBlock:
571
+ Enabled: false
564
572
  Style/MethodCalledOnDoEndBlock:
565
573
  Enabled: false
566
574
  Style/MethodDefParentheses:
@@ -667,6 +675,10 @@ Style/RedundantParentheses:
667
675
  Enabled: false
668
676
  Style/RedundantPercentQ:
669
677
  Enabled: false
678
+ Style/RedundantRegexpCharacterClass:
679
+ Enabled: false
680
+ Style/RedundantRegexpEscape:
681
+ Enabled: false
670
682
  Style/RedundantReturn:
671
683
  Enabled: false
672
684
  Style/RedundantSelf:
@@ -153,12 +153,13 @@ Bundler/GemComment:
153
153
  Description: 'Add a comment describing each gem.'
154
154
  Enabled: false
155
155
  VersionAdded: '0.59'
156
- VersionChanged: '0.77'
156
+ VersionChanged: '0.85'
157
157
  Include:
158
158
  - '**/*.gemfile'
159
159
  - '**/Gemfile'
160
160
  - '**/gems.rb'
161
161
  IgnoredGems: []
162
+ OnlyFor: []
162
163
 
163
164
  Bundler/InsecureProtocolSource:
164
165
  Description: >-
@@ -468,6 +469,13 @@ Layout/EmptyLinesAroundAttributeAccessor:
468
469
  StyleGuide: '#empty-lines-around-attribute-accessor'
469
470
  Enabled: pending
470
471
  VersionAdded: '0.83'
472
+ VersionChanged: '0.84'
473
+ AllowAliasSyntax: true
474
+ AllowedMethods:
475
+ - alias_method
476
+ - public
477
+ - protected
478
+ - private
471
479
 
472
480
  Layout/EmptyLinesAroundBeginBody:
473
481
  Description: "Keeps track of empty lines around begin-end bodies."
@@ -783,13 +791,7 @@ Layout/HeredocIndentation:
783
791
  StyleGuide: '#squiggly-heredocs'
784
792
  Enabled: true
785
793
  VersionAdded: '0.49'
786
- VersionChanged: '0.77'
787
- EnforcedStyle: squiggly
788
- SupportedStyles:
789
- - squiggly
790
- - active_support
791
- - powerpack
792
- - unindent
794
+ VersionChanged: '0.85'
793
795
 
794
796
  Layout/IndentationConsistency:
795
797
  Description: 'Keep indentation straight.'
@@ -857,13 +859,13 @@ Layout/LeadingEmptyLines:
857
859
  VersionChanged: '0.77'
858
860
 
859
861
  Layout/LineLength:
860
- Description: 'Limit lines to 80 characters.'
861
- StyleGuide: '#80-character-limits'
862
+ Description: 'Checks that line length does not exceed the configured limit.'
863
+ StyleGuide: '#max-line-length'
862
864
  Enabled: true
863
865
  VersionAdded: '0.25'
864
- VersionChanged: '0.78'
866
+ VersionChanged: '0.84'
865
867
  AutoCorrect: false
866
- Max: 80
868
+ Max: 120
867
869
  # To make it possible to copy or click on URIs in the code, we allow lines
868
870
  # containing a URI to be longer than Max.
869
871
  AllowHeredoc: true
@@ -1359,6 +1361,15 @@ Lint/CircularArgumentReference:
1359
1361
  Enabled: true
1360
1362
  VersionAdded: '0.33'
1361
1363
 
1364
+ Lint/ConstantResolution:
1365
+ Description: 'Check that constants are fully qualified with `::`.'
1366
+ Enabled: false
1367
+ VersionAdded: '0.86'
1368
+ # Restrict this cop to only looking at certain names
1369
+ Only: []
1370
+ # Restrict this cop from only looking at certain names
1371
+ Ignore: []
1372
+
1362
1373
  Lint/Debugger:
1363
1374
  Description: 'Check for debugger calls.'
1364
1375
  Enabled: true
@@ -1370,6 +1381,11 @@ Lint/DeprecatedClassMethods:
1370
1381
  Enabled: true
1371
1382
  VersionAdded: '0.19'
1372
1383
 
1384
+ Lint/DeprecatedOpenSSLConstant:
1385
+ Description: "Don't use algorithm constants for `OpenSSL::Cipher` and `OpenSSL::Digest`."
1386
+ Enabled: pending
1387
+ VersionAdded: '0.84'
1388
+
1373
1389
  Lint/DisjunctiveAssignmentInConstructor:
1374
1390
  Description: 'In constructor, plain assignment is preferred over disjunctive.'
1375
1391
  Enabled: true
@@ -1526,6 +1542,11 @@ Lint/MissingCopEnableDirective:
1526
1542
  # .inf for any size
1527
1543
  MaximumRangeSize: .inf
1528
1544
 
1545
+ Lint/MixedRegexpCaptureTypes:
1546
+ Description: 'Do not mix named captures and numbered captures in a Regexp literal.'
1547
+ Enabled: pending
1548
+ VersionAdded: '0.85'
1549
+
1529
1550
  Lint/MultipleComparison:
1530
1551
  Description: "Use `&&` operator to compare multiple values."
1531
1552
  Enabled: true
@@ -1599,7 +1620,9 @@ Lint/RaiseException:
1599
1620
  Description: Checks for `raise` or `fail` statements which are raising `Exception` class.
1600
1621
  StyleGuide: '#raise-exception'
1601
1622
  Enabled: pending
1623
+ Safe: false
1602
1624
  VersionAdded: '0.81'
1625
+ VersionChanged: '0.86'
1603
1626
  AllowedImplicitNamespaces:
1604
1627
  - 'Gem'
1605
1628
 
@@ -1656,6 +1679,7 @@ Lint/RegexpAsCondition:
1656
1679
  The regexp literal matches `$_` implicitly.
1657
1680
  Enabled: true
1658
1681
  VersionAdded: '0.51'
1682
+ VersionChanged: '0.86'
1659
1683
 
1660
1684
  Lint/RequireParentheses:
1661
1685
  Description: >-
@@ -1913,7 +1937,7 @@ Metrics/CyclomaticComplexity:
1913
1937
  VersionAdded: '0.25'
1914
1938
  VersionChanged: '0.81'
1915
1939
  IgnoredMethods: []
1916
- Max: 6
1940
+ Max: 7
1917
1941
 
1918
1942
  Metrics/MethodLength:
1919
1943
  Description: 'Avoid methods longer than 10 lines of code.'
@@ -1999,6 +2023,11 @@ Naming/ClassAndModuleCamelCase:
1999
2023
  StyleGuide: '#camelcase-classes'
2000
2024
  Enabled: true
2001
2025
  VersionAdded: '0.50'
2026
+ VersionChanged: '0.85'
2027
+ # Allowed class/module names can be specified here.
2028
+ # These can be full or part of the name.
2029
+ AllowedNames:
2030
+ - module_parent
2002
2031
 
2003
2032
  Naming/ConstantName:
2004
2033
  Description: 'Constants should use SCREAMING_SNAKE_CASE.'
@@ -2019,6 +2048,10 @@ Naming/FileName:
2019
2048
  # It further expects it to be nested inside modules which match the names
2020
2049
  # of subdirectories in its path.
2021
2050
  ExpectMatchingDefinition: false
2051
+ # When `false`, changes the behavior of ExpectMatchingDefinition to match only
2052
+ # whether each source file's class or module name matches the file name --
2053
+ # not whether the nested module hierarchy matches the subdirectory path.
2054
+ CheckDefinitionPathHierarchy: true
2022
2055
  # If non-`nil`, expect all source file names to match the following regex.
2023
2056
  # Only the file name itself is matched, not the entire file path.
2024
2057
  # Use anchors as necessary if you want to match the entire name rather than
@@ -2210,7 +2243,7 @@ Security/JSONLoad:
2210
2243
  Description: >-
2211
2244
  Prefer usage of `JSON.parse` over `JSON.load` due to potential
2212
2245
  security issues. See reference for more information.
2213
- Reference: 'https://ruby-doc.org/stdlib-2.3.0/libdoc/json/rdoc/JSON.html#method-i-load'
2246
+ Reference: 'https://ruby-doc.org/stdlib-2.7.0/libdoc/json/rdoc/JSON.html#method-i-load'
2214
2247
  Enabled: true
2215
2248
  VersionAdded: '0.43'
2216
2249
  VersionChanged: '0.44'
@@ -2223,7 +2256,7 @@ Security/MarshalLoad:
2223
2256
  Description: >-
2224
2257
  Avoid using of `Marshal.load` or `Marshal.restore` due to potential
2225
2258
  security issues. See reference for more information.
2226
- Reference: 'https://ruby-doc.org/core-2.3.3/Marshal.html#module-Marshal-label-Security+considerations'
2259
+ Reference: 'https://ruby-doc.org/core-2.7.0/Marshal.html#module-Marshal-label-Security+considerations'
2227
2260
  Enabled: true
2228
2261
  VersionAdded: '0.47'
2229
2262
 
@@ -2237,7 +2270,7 @@ Security/YAMLLoad:
2237
2270
  Description: >-
2238
2271
  Prefer usage of `YAML.safe_load` over `YAML.load` due to potential
2239
2272
  security issues. See reference for more information.
2240
- Reference: 'https://ruby-doc.org/stdlib-2.3.3/libdoc/yaml/rdoc/YAML.html#module-YAML-label-Security'
2273
+ Reference: 'https://ruby-doc.org/stdlib-2.7.0/libdoc/yaml/rdoc/YAML.html#module-YAML-label-Security'
2241
2274
  Enabled: true
2242
2275
  VersionAdded: '0.47'
2243
2276
  SafeAutoCorrect: false
@@ -2274,7 +2307,7 @@ Style/AndOr:
2274
2307
  VersionChanged: '0.25'
2275
2308
  # Whether `and` and `or` are banned only in conditionals (conditionals)
2276
2309
  # or completely (always).
2277
- EnforcedStyle: always
2310
+ EnforcedStyle: conditionals
2278
2311
  SupportedStyles:
2279
2312
  - always
2280
2313
  - conditionals
@@ -2694,6 +2727,11 @@ Style/DoubleNegation:
2694
2727
  StyleGuide: '#no-bang-bang'
2695
2728
  Enabled: true
2696
2729
  VersionAdded: '0.19'
2730
+ VersionChanged: '0.84'
2731
+ EnforcedStyle: allowed_in_returns
2732
+ SupportedStyles:
2733
+ - allowed_in_returns
2734
+ - forbidden
2697
2735
 
2698
2736
  Style/EachForSimpleLoop:
2699
2737
  Description: >-
@@ -2857,8 +2895,7 @@ Style/FrozenStringLiteralComment:
2857
2895
  SupportedStyles:
2858
2896
  # `always` will always add the frozen string literal comment to a file
2859
2897
  # regardless of the Ruby version or if `freeze` or `<<` are called on a
2860
- # string literal. If you run code against multiple versions of Ruby, it is
2861
- # possible that this will create errors in Ruby 2.3.0+.
2898
+ # string literal. It is possible that this will create errors.
2862
2899
  - always
2863
2900
  # `always_true` will add the frozen string literal comment to a file,
2864
2901
  # similarly to the `always` style, but will also change any disabled
@@ -3214,6 +3251,7 @@ Style/MultilineTernaryOperator:
3214
3251
  StyleGuide: '#no-multiline-ternary'
3215
3252
  Enabled: true
3216
3253
  VersionAdded: '0.9'
3254
+ VersionChanged: '0.86'
3217
3255
 
3218
3256
  Style/MultilineWhenThen:
3219
3257
  Description: 'Do not use then for multi-line when statement.'
@@ -3318,6 +3356,7 @@ Style/NestedTernaryOperator:
3318
3356
  StyleGuide: '#no-nested-ternary'
3319
3357
  Enabled: true
3320
3358
  VersionAdded: '0.9'
3359
+ VersionChanged: '0.86'
3321
3360
 
3322
3361
  Style/Next:
3323
3362
  Description: 'Use `next` to skip iteration instead of a condition at the end.'
@@ -3571,6 +3610,19 @@ Style/RedundantException:
3571
3610
  VersionAdded: '0.14'
3572
3611
  VersionChanged: '0.29'
3573
3612
 
3613
+ Style/RedundantFetchBlock:
3614
+ Description: >-
3615
+ Use `fetch(key, value)` instead of `fetch(key) { value }`
3616
+ when value has Numeric, Rational, Complex, Symbol or String type, `false`, `true`, `nil` or is a constant.
3617
+ Reference: 'https://github.com/JuanitoFatas/fast-ruby#hashfetch-with-argument-vs-hashfetch--block-code'
3618
+ Enabled: 'pending'
3619
+ Safe: false
3620
+ # If enabled, this cop will autocorrect usages of
3621
+ # `fetch` being called with block returning a constant.
3622
+ # This can be dangerous since constants will not be defined at that moment.
3623
+ SafeForConstants: false
3624
+ VersionAdded: '0.86'
3625
+
3574
3626
  Style/RedundantFreeze:
3575
3627
  Description: "Checks usages of Object#freeze on immutable objects."
3576
3628
  Enabled: true
@@ -3593,6 +3645,16 @@ Style/RedundantPercentQ:
3593
3645
  Enabled: true
3594
3646
  VersionAdded: '0.76'
3595
3647
 
3648
+ Style/RedundantRegexpCharacterClass:
3649
+ Description: 'Checks for unnecessary single-element Regexp character classes.'
3650
+ Enabled: pending
3651
+ VersionAdded: '0.85'
3652
+
3653
+ Style/RedundantRegexpEscape:
3654
+ Description: 'Checks for redundant escapes in Regexps.'
3655
+ Enabled: pending
3656
+ VersionAdded: '0.85'
3657
+
3596
3658
  Style/RedundantReturn:
3597
3659
  Description: "Don't use return where it's not required."
3598
3660
  StyleGuide: '#no-explicit-return'
@@ -3840,6 +3902,7 @@ Style/StructInheritance:
3840
3902
  StyleGuide: '#no-extend-struct-new'
3841
3903
  Enabled: true
3842
3904
  VersionAdded: '0.29'
3905
+ VersionChanged: '0.86'
3843
3906
 
3844
3907
  Style/SymbolArray:
3845
3908
  Description: 'Use %i or %I for arrays of symbols.'
@@ -11,7 +11,7 @@ require 'rubocop/monkey_patches/comment_config.rb'
11
11
  # monkey patches needed for the TargetChefVersion config option
12
12
  require 'rubocop/monkey_patches/config.rb'
13
13
  require 'rubocop/monkey_patches/cop.rb'
14
- require 'rubocop/monkey_patches/commissioner.rb'
14
+ require 'rubocop/monkey_patches/team.rb'
15
15
 
16
16
  module RuboCop
17
17
  class ConfigLoader
@@ -1,4 +1,4 @@
1
1
  module Cookstyle
2
- VERSION = "6.4.4".freeze # rubocop: disable Style/StringLiterals
3
- RUBOCOP_VERSION = '0.83.0'.freeze
2
+ VERSION = "6.9.0".freeze # rubocop: disable Style/StringLiterals
3
+ RUBOCOP_VERSION = '0.86.0'.freeze
4
4
  end
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright:: Copyright 2019, Chef Software Inc.
2
+ # Copyright:: Copyright 2019-2020, Chef Software Inc.
3
3
  # Author:: Tim Smith (<tsmith@chef.io>)
4
4
  #
5
5
  # Licensed under the Apache License, Version 2.0 (the "License");
@@ -32,6 +32,7 @@ module RuboCop
32
32
  'mswin' => 'windows',
33
33
  'opensuse' => 'suse',
34
34
  'opensuseleap' => 'suse',
35
+ 'oracle' => 'rhel',
35
36
  'redhat' => 'rhel',
36
37
  'scientific' => 'rhel',
37
38
  'sles' => 'suse',
@@ -0,0 +1,77 @@
1
+ #
2
+ # Copyright:: Copyright 2020, Chef Software Inc.
3
+ # Author:: Tim Smith (<tsmith@chef.io>)
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ module RuboCop
18
+ module Cop
19
+ module Chef
20
+ module ChefCorrectness
21
+ # Use valid platform family values in case statements.
22
+ #
23
+ # @example
24
+ #
25
+ # # bad
26
+ # case node['platform_family']
27
+ # when 'redhat'
28
+ # puts "I'm on a RHEL-like system"
29
+ # end
30
+ #
31
+ class InvalidPlatformFamilyInCase < Cop
32
+ include RangeHelp
33
+ include ::RuboCop::Chef::PlatformHelpers
34
+
35
+ MSG = 'Use valid platform family values in case statements.'.freeze
36
+
37
+ def_node_matcher :node_platform_family?, <<-PATTERN
38
+ (send (send nil? :node) :[] (str "platform_family") )
39
+ PATTERN
40
+
41
+ def on_case(node)
42
+ node_platform_family?(node.condition) do
43
+ node.each_when do |when_node|
44
+ when_node.each_condition do |con|
45
+ next unless con.str_type? # if the condition isn't a string we can't check so skip
46
+
47
+ if INVALID_PLATFORM_FAMILIES[con.str_content]
48
+ add_offense(con, location: :expression, message: MSG, severity: :refactor)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def autocorrect(node)
56
+ new_value = INVALID_PLATFORM_FAMILIES[node.str_content]
57
+
58
+ # some invalid platform families have no direct correction value and return nil instead
59
+ return unless new_value
60
+
61
+ # if the correct value already exists in the when statement then we just want to delete this node
62
+ if node.parent.conditions.any? { |x| x.str_content == new_value }
63
+ lambda do |corrector|
64
+ range = range_with_surrounding_comma(range_with_surrounding_space(range: node.loc.expression, side: :left), :both)
65
+ corrector.remove(range)
66
+ end
67
+ else
68
+ lambda do |corrector|
69
+ corrector.replace(node.loc.expression, "'#{new_value}'")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end
@@ -0,0 +1,77 @@
1
+ #
2
+ # Copyright:: Copyright 2020, Chef Software Inc.
3
+ # Author:: Tim Smith (<tsmith@chef.io>)
4
+ #
5
+ # Licensed under the Apache License, Version 2.0 (the "License");
6
+ # you may not use this file except in compliance with the License.
7
+ # You may obtain a copy of the License at
8
+ #
9
+ # http://www.apache.org/licenses/LICENSE-2.0
10
+ #
11
+ # Unless required by applicable law or agreed to in writing, software
12
+ # distributed under the License is distributed on an "AS IS" BASIS,
13
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14
+ # See the License for the specific language governing permissions and
15
+ # limitations under the License.
16
+ #
17
+ module RuboCop
18
+ module Cop
19
+ module Chef
20
+ module ChefCorrectness
21
+ # Use valid platform values in case statements.
22
+ #
23
+ # @example
24
+ #
25
+ # # bad
26
+ # case node['platform']
27
+ # when 'rhel'
28
+ # puts "I'm on a Red Hat system!"
29
+ # end
30
+ #
31
+ class InvalidPlatformInCase < Cop
32
+ include RangeHelp
33
+ include ::RuboCop::Chef::PlatformHelpers
34
+
35
+ MSG = 'Use valid platform values in case statements.'.freeze
36
+
37
+ def_node_matcher :node_platform?, <<-PATTERN
38
+ (send (send nil? :node) :[] (str "platform") )
39
+ PATTERN
40
+
41
+ def on_case(node)
42
+ node_platform?(node.condition) do
43
+ node.each_when do |when_node|
44
+ when_node.each_condition do |con|
45
+ next unless con.str_type? # if the condition isn't a string we can't check so skip
46
+
47
+ if INVALID_PLATFORMS[con.str_content]
48
+ add_offense(con, location: :expression, message: MSG, severity: :refactor)
49
+ end
50
+ end
51
+ end
52
+ end
53
+ end
54
+
55
+ def autocorrect(node)
56
+ new_value = INVALID_PLATFORMS[node.str_content]
57
+
58
+ # some invalid platform have no direct correction value and return nil instead
59
+ return unless new_value
60
+
61
+ # if the correct value already exists in the when statement then we just want to delete this node
62
+ if node.parent.conditions.any? { |x| x.str_content == new_value }
63
+ lambda do |corrector|
64
+ range = range_with_surrounding_comma(range_with_surrounding_space(range: node.loc.expression, side: :left), :both)
65
+ corrector.remove(range)
66
+ end
67
+ else
68
+ lambda do |corrector|
69
+ corrector.replace(node.loc.expression, "'#{new_value}'")
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
76
+ end
77
+ end