cookstyle 4.0.0 → 5.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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