cookstyle 4.0.0 → 5.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,8 @@
1
1
  require 'cookstyle/version'
2
2
 
3
+ require 'pathname'
4
+ require 'yaml'
5
+
3
6
  # ensure the desired target version of RuboCop is gem activated
4
7
  gem 'rubocop', "= #{Cookstyle::RUBOCOP_VERSION}"
5
8
  require 'rubocop'
@@ -21,9 +24,19 @@ end
21
24
  # Cookstyle patches the RuboCop tool to set a new default configuration that
22
25
  # is vendored in the Cookstyle codebase.
23
26
  module Cookstyle
24
- # @return [String] the absolute path to the main RuboCop configuration YAML
25
- # file
27
+ # @return [String] the absolute path to the main RuboCop configuration YAML file
26
28
  def self.config
27
29
  RuboCop::ConfigLoader::DEFAULT_FILE
28
30
  end
29
31
  end
32
+
33
+ require 'rubocop/chef'
34
+ require 'rubocop/chef/cookbook_only'
35
+
36
+ # Chef specific cops
37
+ require 'rubocop/cop/chef/attribute_keys'
38
+ require 'rubocop/cop/chef/file_mode'
39
+ require 'rubocop/cop/chef/service_resource'
40
+ require 'rubocop/cop/chef/comments_format'
41
+ require 'rubocop/cop/chef/comments_copyright_format'
42
+ require 'rubocop/cop/chef/tmp_path'
@@ -1,4 +1,4 @@
1
1
  module Cookstyle
2
- VERSION = "4.0.0".freeze # rubocop: disable Style/StringLiterals
3
- RUBOCOP_VERSION = '0.62.0'.freeze
2
+ VERSION = '5.0.0'.freeze
3
+ RUBOCOP_VERSION = '0.72.0'.freeze
4
4
  end
@@ -0,0 +1,10 @@
1
+ module RuboCop
2
+ # RuboCop Chef project namespace
3
+ module Chef
4
+ PROJECT_ROOT = Pathname.new(__dir__).parent.parent.expand_path.freeze
5
+ CONFIG_DEFAULT = PROJECT_ROOT.join('config', 'cookstyle.yml').freeze
6
+ CONFIG = YAML.load(CONFIG_DEFAULT.read).freeze
7
+
8
+ private_constant(*constants(false))
9
+ end
10
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RuboCop
4
+ module Chef
5
+ # Mixin for cops that skips non-cookbook files
6
+ #
7
+ # The criteria for whether rubocop-rspec analyzes a certain ruby file
8
+ # is configured via `AllCops/RSpec`. For example, if you want to
9
+ # customize your project to scan all files within a `test/` directory
10
+ # then you could add this to your configuration:
11
+ #
12
+ # @example configuring analyzed paths
13
+ #
14
+ # AllCops:
15
+ # RSpec:
16
+ # Patterns:
17
+ # - '_spec.rb$'
18
+ # - '(?:^|/)spec/'
19
+ #
20
+ module CookbookOnly
21
+ DEFAULT_CONFIGURATION = CONFIG.fetch('AllCops')
22
+ COOKBOOK_SEGMENTS = %w(attributes definitions libraries metadata providers recipes resources).freeze
23
+
24
+ def relevant_file?(file)
25
+ cookbook_pattern =~ file && super
26
+ end
27
+
28
+ private
29
+
30
+ def cookbook_pattern
31
+ patterns = []
32
+ COOKBOOK_SEGMENTS.each do |segment|
33
+ next unless self.class.cookbook_only_segments[segment.to_sym]
34
+ cookbook_pattern_config(segment).each do |pattern|
35
+ patterns << Regexp.new(pattern)
36
+ end
37
+ end
38
+ Regexp.union(patterns)
39
+ end
40
+
41
+ def cookbook_pattern_config(segment)
42
+ config_key = "Chef#{segment.capitalize}"
43
+ config
44
+ .for_all_cops
45
+ .fetch(config_key, DEFAULT_CONFIGURATION.fetch(config_key))
46
+ .fetch('Patterns')
47
+ end
48
+
49
+ module ClassMethods
50
+ attr_writer :cookbook_only_segments
51
+
52
+ def cookbook_only_segments
53
+ @cookbook_only_segments || Hash.new(true)
54
+ end
55
+
56
+ def included(klass)
57
+ super
58
+ klass.extend(ClassMethods)
59
+ end
60
+ end
61
+
62
+ extend ClassMethods
63
+ end
64
+
65
+ def self.CookbookOnly(segments) # rubocop: disable Naming/MethodName
66
+ Module.new do |mod|
67
+ mod.define_singleton_method(:included) do |klass|
68
+ super(klass)
69
+ klass.include(CookbookOnly)
70
+ klass.cookbook_only_segments = segments
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,92 @@
1
+ #
2
+ # Copyright:: 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module RuboCop
18
+ module Cop
19
+ module Chef
20
+ # Check which style of keys are used to access node attributes.
21
+ #
22
+ # There are two supported styles: "symbols" and "strings".
23
+ #
24
+ # @example when configuration is `EnforcedStyle: symbols`
25
+ #
26
+ # # bad
27
+ # node['foo']
28
+ # node["foo"]
29
+ #
30
+ # # good
31
+ # node[:foo]
32
+ #
33
+ # @example when configuration is `EnforcedStyle: strings`
34
+ #
35
+ # # bad
36
+ # node[:foo]
37
+ #
38
+ # # good
39
+ # node['foo']
40
+ # node["foo"]
41
+ #
42
+ class AttributeKeys < Cop
43
+ include RuboCop::Cop::ConfigurableEnforcedStyle
44
+
45
+ MSG = 'Use %s to access node attributes'.freeze
46
+
47
+ def_node_matcher :node_attribute_access?, <<-PATTERN
48
+ (send (send _ :node) :[] _)
49
+ PATTERN
50
+
51
+ def_node_matcher :node_level_attribute_access?, <<-PATTERN
52
+ (send (send {(send _ :node) nil} {:default :role_default :env_default :normal :override :role_override :env_override :force_override :automatic}) :[] _)
53
+ PATTERN
54
+
55
+ def on_send(node)
56
+ if node_attribute_access?(node) || node_level_attribute_access?(node)
57
+ # node is first child for #[], need to look for the outermost parent too.
58
+ outer_node = node
59
+ while outer_node.parent && outer_node.parent.type == :send && outer_node.parent.children[1] == :[]
60
+ on_node_attribute_access(outer_node.children[2])
61
+ outer_node = outer_node.parent
62
+ end
63
+ # One last time for the outer-most access.
64
+ on_node_attribute_access(outer_node.children[2])
65
+ end
66
+ end
67
+
68
+ def on_node_attribute_access(node)
69
+ if node.type == :str
70
+ style_detected(:strings)
71
+ add_offense(node, location: :expression, message: MSG % style, severity: :warning) if style == :symbols
72
+ elsif node.type == :sym
73
+ style_detected(:symbols)
74
+ add_offense(node, location: :expression, message: MSG % style, severity: :warning) if style == :strings
75
+ end
76
+ end
77
+
78
+ def autocorrect(node)
79
+ lambda do |corrector|
80
+ key_string = node.children.first.to_s
81
+ key_replacement = if style == :symbols
82
+ key_string.to_sym.inspect
83
+ else # strings
84
+ key_string.inspect
85
+ end
86
+ corrector.replace(node.loc.expression, key_replacement)
87
+ end
88
+ end
89
+ end
90
+ end
91
+ end
92
+ end
@@ -0,0 +1,99 @@
1
+ #
2
+ # Copyright:: 2016, Tim Smith
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module RuboCop
18
+ module Cop
19
+ module Chef
20
+ # Checks for incorrectly formatted copyright comments.
21
+ #
22
+ # @example
23
+ #
24
+ # # bad (assuming current year is 2016)
25
+ # Copyright:: 2013-2016 Opscode, Inc.
26
+ # Copyright:: 2013-2016 Chef Inc.
27
+ # Copyright:: 2013-2016 Chef Software Inc.
28
+ # Copyright:: 2009-2010 2013-2016 Chef Software Inc.
29
+ # Copyright:: Chef Software Inc.
30
+ # Copyright:: Tim Smith
31
+ # Copyright:: Copyright (c) 2015-2016 Chef Software, Inc.
32
+ #
33
+ # # good (assuming current year is 2016)
34
+ # Copyright:: 2013-2016 Chef Software, Inc.
35
+ # Copyright:: 2013-2016 Tim Smith
36
+ # Copyright:: 2016 37Signals, Inc.
37
+ #
38
+ class CopyrightCommentFormat < Cop
39
+ require 'date'
40
+
41
+ MSG = 'Properly format copyrights header comments'.freeze
42
+
43
+ def investigate(processed_source)
44
+ return unless processed_source.ast
45
+ processed_source.comments.each do |comment|
46
+ next unless comment.inline? # headers aren't in blocks
47
+
48
+ if invalid_copyright_comment?(comment)
49
+ add_offense(comment, location: comment.loc.expression, message: MSG, severity: :warning)
50
+ end
51
+ end
52
+ end
53
+
54
+ def autocorrect(comment)
55
+ correct_comment = "# Copyright:: #{copyright_date_range(comment)}, #{copyright_holder(comment)}"
56
+ ->(corrector) { corrector.replace(comment.loc.expression, correct_comment) }
57
+ end
58
+
59
+ private
60
+
61
+ def copyright_date_range(comment)
62
+ dates = comment.text.scan(/([0-9]{4})/)
63
+
64
+ # no copyright year present so return this year
65
+ return Time.new.year if dates.empty?
66
+ oldest_date = dates.min[0].to_i
67
+
68
+ # Avoid returning THIS_YEAR - THIS_YEAR
69
+ if oldest_date == Time.new.year
70
+ oldest_date
71
+ else
72
+ "#{oldest_date}-#{Time.new.year}"
73
+ end
74
+ end
75
+
76
+ def copyright_holder(comment)
77
+ # Grab just the company / individual name w/o :: or dates
78
+ match = /(?:.*[0-9]{4}|Copyright\W*)(?:,)?(?:\s)?(.*)/.match(comment.text)
79
+ marketing_sanitizer(match.captures[0])
80
+ end
81
+
82
+ # Flush Opscode down the memory hole and Chef Inc is not a company
83
+ def marketing_sanitizer(name)
84
+ name.gsub('Opscode', 'Chef Software').gsub(/Chef(?:,)? Inc.*/, 'Chef Software, Inc.')
85
+ end
86
+
87
+ def invalid_copyright_comment?(comment)
88
+ match = /# (?:Copyright\W*)(.*)/.match(comment.text)
89
+ return false unless match # it's not even a copyright
90
+
91
+ current_text = match.captures[0]
92
+ desired_text = "#{copyright_date_range(comment)}, #{copyright_holder(comment)}"
93
+
94
+ return true unless current_text == desired_text
95
+ end
96
+ end
97
+ end
98
+ end
99
+ end
@@ -0,0 +1,75 @@
1
+ #
2
+ # Copyright 2016, Tim Smith
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module RuboCop
18
+ module Cop
19
+ module Chef
20
+ # Checks for incorrectly formatted headers
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # Copyright 2013-2016 Chef Software, Inc.
26
+ # Recipe default.rb
27
+ # Attributes default.rb
28
+ # License Apache2
29
+ # Cookbook tomcat
30
+ # Cookbook Name:: Tomcat
31
+ # Attributes File:: default
32
+ #
33
+ # # good
34
+ # Copyright:: 2013-2016 Chef Software, Inc.
35
+ # Recipe:: default.rb
36
+ # Attributes:: default.rb
37
+ # License:: Apache License, Version 2.0
38
+ # Cookbook:: Tomcat
39
+ #
40
+ class CommentFormat < Cop
41
+ MSG = 'Properly format header comments'.freeze
42
+
43
+ def investigate(processed_source)
44
+ return unless processed_source.ast
45
+ processed_source.comments.each do |comment|
46
+ next unless comment.inline? # headers aren't in blocks
47
+
48
+ if invalid_comment?(comment)
49
+ add_offense(comment, location: comment.loc.expression, message: MSG, severity: :warning)
50
+ end
51
+ end
52
+ end
53
+
54
+ def autocorrect(comment)
55
+ # Extract the type and the actual value. Strip out "Name" or "File"
56
+ # 'Cookbook Name' should be 'Cookbook'. Also skip a :: if present
57
+ match = /^# ?([A-Za-z]+)\s?(?:Name|File)?(?:::)?\s(.*)/.match(comment.text)
58
+ comment_type, value = match.captures
59
+ correct_comment = "# #{comment_type}:: #{value}"
60
+
61
+ ->(corrector) { corrector.replace(comment.loc.expression, correct_comment) }
62
+ end
63
+
64
+ private
65
+
66
+ def invalid_comment?(comment)
67
+ comment_types = %w(Author Cookbook Library Attribute Copyright Recipe Resource Definition License)
68
+ comment_types.any? do |comment_type|
69
+ /^#\s*#{comment_type}\s+/.match(comment.text)
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,63 @@
1
+ #
2
+ # Copyright:: 2016, Noah Kantrowitz
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ #
16
+
17
+ module RuboCop
18
+ module Cop
19
+ module Chef
20
+ # Check the file modes are given as strings instead of integers.
21
+ #
22
+ # @example
23
+ #
24
+ # # bad
25
+ # mode 644
26
+ # mode 0644
27
+ #
28
+ # # good
29
+ # mode '644'
30
+ #
31
+ class FileMode < Cop
32
+ MSG = 'Use strings for file modes'.freeze
33
+
34
+ def_node_matcher :resource_mode?, <<-PATTERN
35
+ (send nil :mode $int)
36
+ PATTERN
37
+
38
+ def on_send(node)
39
+ resource_mode?(node) do |mode_int|
40
+ # for post April 2020
41
+ # add_offense(mode_int, location: :expression, message: MSG, severity: octal?(mode_int) ? :warning : :error)
42
+ add_offense(mode_int, location: :expression, message: MSG, severity: :warning)
43
+ end
44
+ end
45
+
46
+ def autocorrect(node)
47
+ lambda do |corrector|
48
+ # If it was an octal literal, make sure we write out the right number.
49
+ replacement_base = octal?(node) ? 8 : 10
50
+ replacement_mode = node.children.first.to_s(replacement_base)
51
+ corrector.replace(node.loc.expression, replacement_mode.inspect)
52
+ end
53
+ end
54
+
55
+ private
56
+
57
+ def octal?(node)
58
+ node.source =~ /^0o?\d+/i
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end