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.
- checksums.yaml +4 -4
- data/config/cookstyle.yml +49 -55
- data/config/disable_all.yml +64 -152
- data/config/upstream.yml +310 -728
- data/lib/cookstyle.rb +15 -2
- data/lib/cookstyle/version.rb +2 -2
- data/lib/rubocop/chef.rb +10 -0
- data/lib/rubocop/chef/cookbook_only.rb +75 -0
- data/lib/rubocop/cop/chef/attribute_keys.rb +92 -0
- data/lib/rubocop/cop/chef/comments_copyright_format.rb +99 -0
- data/lib/rubocop/cop/chef/comments_format.rb +75 -0
- data/lib/rubocop/cop/chef/file_mode.rb +63 -0
- data/lib/rubocop/cop/chef/service_resource.rb +53 -0
- data/lib/rubocop/cop/chef/tmp_path.rb +58 -0
- metadata +13 -8
- data/config/disabled.yml +0 -128
- data/config/enabled.yml +0 -2068
data/lib/cookstyle.rb
CHANGED
@@ -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'
|
data/lib/cookstyle/version.rb
CHANGED
data/lib/rubocop/chef.rb
ADDED
@@ -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
|