puppet-lint 3.0.1 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 780fb18c627fc4498cfbd056762846a70b2240619c52a23121d522b56aaec062
4
- data.tar.gz: 59068f03812ef0d9b622fa6b9440567ba4e12f33a5349c1ddc9c642ac7dd51a5
3
+ metadata.gz: e8eaced98bf8f51da32089208cd7d628100e532e91d7323b77d04264584efecb
4
+ data.tar.gz: b4edfa7027559c4ccc1f0f25433558069857e9cbb6ef17ddcad85a9db1ee795f
5
5
  SHA512:
6
- metadata.gz: 32fb18937f47636f80535bcc8db5884aa63a6af9a86f148ee25a37b65890d13f275094ab2a81012a5ef7385d696195c59632d234eca805c94a2bc2d3b4d77905
7
- data.tar.gz: 124a921b4ad50b3f923f7180d330a89f313f70c5b77d1dcf8060611a124171e1bf3ef06f8b3d4b27421cad4157c69699c12bc8acc8dd63461a61fb1aecacc14c
6
+ metadata.gz: 3b88dcb4eee4df0475c83764f456a3a9f2721bbee922ca8b7cb981225d07c4be674ffc4f929a45ec6e357b4c7ca702e1c88ffbbe44f4e52dd3f54836233852d4
7
+ data.tar.gz: dc0278aea71db1cab2c2de6788d58ab51c15a25567c912ec5fd3456c60a2c30eb1e43c2b55015bf242a1da9217c5f4c8c8c409dfb777504730f9ed3ae300520b
data/README.md CHANGED
@@ -9,7 +9,9 @@ guide](http://puppet.com/docs/puppet/latest/style_guide.html). Puppet Lint valid
9
9
 
10
10
  ## Compatibility warning
11
11
 
12
- Puppet Lint version 2 is the last planned version with support for Puppet 3 and Ruby 1.8.7. The next major version of Puppet Lint will drop support for these versions.
12
+ Version 3.0.0 and above will no longer support Puppet 6 environments.
13
+
14
+ In cases where Puppet Lint is required in an environment with Puppet 6, we recommend pinning to version 2.5.2.
13
15
 
14
16
  ## Installation
15
17
 
@@ -213,6 +215,30 @@ There is a GitHub Actions action available to get linter feedback in workflows:
213
215
 
214
216
  * [puppet-lint-action](https://github.com/marketplace/actions/puppet-lint-action)
215
217
 
218
+ ## Integration with GitLab Code Quality
219
+
220
+ [GitLab](https://gitlab.com/) users can use the `--codeclimate-report-file` configuration option to generate a report for use with the
221
+ [Code Quality](https://docs.gitlab.com/ee/ci/testing/code_quality.html) feature.
222
+
223
+ The easiest way to set this option, (and without having to modify rake tasks), is with the `CODECLIMATE_REPORT_FILE` environment variable.
224
+
225
+ For example, the following GitLab job sets the environment variable and
226
+ [archives the report](https://docs.gitlab.com/ee/ci/yaml/artifacts_reports.html#artifactsreportscodequality) produced.
227
+ ```yaml
228
+ validate lint check rubocop-Ruby 2.7.2-Puppet ~> 7:
229
+ stage: syntax
230
+ image: ruby:2.7.2
231
+ script:
232
+ - bundle exec rake validate lint check rubocop
233
+ variables:
234
+ PUPPET_GEM_VERSION: '~> 7'
235
+ CODECLIMATE_REPORT_FILE: 'gl-code-quality-report.json'
236
+ artifacts:
237
+ reports:
238
+ codequality: gl-code-quality-report.json
239
+ expire_in: 1 week
240
+ ```
241
+
216
242
  ## Options
217
243
 
218
244
  See `puppet-lint --help` for a full list of command line options and checks.
@@ -1,6 +1,7 @@
1
1
  require 'pathname'
2
2
  require 'uri'
3
3
  require 'puppet-lint/optparser'
4
+ require 'puppet-lint/report/codeclimate'
4
5
 
5
6
  # Internal: The logic of the puppet-lint bin script, contained in a class for
6
7
  # ease of testing.
@@ -104,6 +105,10 @@ class PuppetLint::Bin
104
105
  puts JSON.pretty_generate(all_problems)
105
106
  end
106
107
 
108
+ if PuppetLint.configuration.codeclimate_report_file
109
+ PuppetLint::Report::CodeClimateReporter.write_report_file(all_problems, PuppetLint.configuration.codeclimate_report_file)
110
+ end
111
+
107
112
  return_val
108
113
  rescue PuppetLint::NoCodeError
109
114
  puts 'puppet-lint: no file specified or specified file does not exist'
@@ -153,5 +153,6 @@ class PuppetLint::Configuration
153
153
  self.show_ignored = false
154
154
  self.ignore_paths = ['vendor/**/*.pp']
155
155
  self.github_actions = ENV.key?('GITHUB_ACTION')
156
+ self.codeclimate_report_file = ENV['CODECLIMATE_REPORT_FILE']
156
157
  end
157
158
  end
@@ -155,6 +155,18 @@ class PuppetLint::Data
155
155
  end
156
156
  end
157
157
 
158
+ # Internal: Determine if the given token contains a CLASSREF in
159
+ # the token chain..
160
+ #
161
+ # Returns a Boolean.
162
+ def classref?(token)
163
+ current_token = token
164
+ while (current_token = current_token.prev_code_token)
165
+ return true if current_token.type == :CLASSREF
166
+ return false if current_token.type == :NAME
167
+ end
168
+ end
169
+
158
170
  # Internal: Calculate the positions of all resource declarations within the
159
171
  # tokenised manifest. These positions only point to the content of the
160
172
  # resource declarations, they do not include resource types or titles.
@@ -170,6 +182,7 @@ class PuppetLint::Data
170
182
  result = []
171
183
  tokens.select { |t| t.type == :COLON }.each do |colon_token|
172
184
  next unless colon_token.next_code_token && colon_token.next_code_token.type != :LBRACE
185
+ next if classref?(colon_token)
173
186
 
174
187
  rel_start_idx = tokens[marker..-1].index(colon_token)
175
188
  break if rel_start_idx.nil?
@@ -102,6 +102,10 @@ class PuppetLint::OptParser
102
102
  PuppetLint.configuration.sarif = true
103
103
  end
104
104
 
105
+ opts.on('--codeclimate-report-file FILE', 'Save a code climate compatible report to this file') do |file|
106
+ PuppetLint.configuration.codeclimate_report_file = file
107
+ end
108
+
105
109
  opts.on('--list-checks', 'List available check names.') do
106
110
  PuppetLint.configuration.list_checks = true
107
111
  end
@@ -30,7 +30,7 @@ PuppetLint.new_check(:trailing_whitespace) do
30
30
 
31
31
  prev_token = problem[:token].prev_token
32
32
  next_token = problem[:token].next_token
33
- prev_token.next_token = next_token
33
+ prev_token.next_token = next_token unless prev_token.nil?
34
34
  next_token.prev_token = prev_token unless next_token.nil?
35
35
  tokens.delete(problem[:token])
36
36
  end
@@ -0,0 +1,195 @@
1
+ # Public: A puppet-lint custom check to detect legacy facts.
2
+ #
3
+ # This check will optionally convert from legacy facts like $::operatingsystem
4
+ # or legacy hashed facts like $facts['operatingsystem'] to the
5
+ # new structured facts like $facts['os']['name'].
6
+ #
7
+ # This plugin was adopted in to puppet-lint from https://github.com/mmckinst/puppet-lint-legacy_facts-check
8
+ # Thanks to @mmckinst, @seanmil, @rodjek, @baurmatt, @bart2 and @joshcooper for the original work.
9
+ PuppetLint.new_check(:legacy_facts) do
10
+ LEGACY_FACTS_VAR_TYPES = Set[:VARIABLE, :UNENC_VARIABLE]
11
+
12
+ # These facts that can't be converted to new facts.
13
+ UNCONVERTIBLE_FACTS = ['memoryfree_mb', 'memorysize_mb', 'swapfree_mb',
14
+ 'swapsize_mb', 'blockdevices', 'interfaces', 'zones',
15
+ 'sshfp_dsa', 'sshfp_ecdsa', 'sshfp_ed25519',
16
+ 'sshfp_rsa'].freeze
17
+
18
+ # These facts will depend on how a system is set up and can't just be
19
+ # enumerated like the EASY_FACTS below.
20
+ #
21
+ # For example a server might have two block devices named 'sda' and 'sdb' so
22
+ # there would be a $blockdeivce_sda_vendor and $blockdeivce_sdb_vendor fact
23
+ # for each device. Or it could have 26 block devices going all the way up to
24
+ # 'sdz'. There is no way to know what the possibilities are so we have to use
25
+ # a regex to match them.
26
+ REGEX_FACTS = [%r{^blockdevice_(?<devicename>.*)_(?<attribute>model|size|vendor)$},
27
+ %r{^(?<attribute>ipaddress|ipaddress6|macaddress|mtu|netmask|netmask6|network|network6)_(?<interface>.*)$},
28
+ %r{^processor(?<id>[0-9]+)$},
29
+ %r{^sp_(?<name>.*)$},
30
+ %r{^ssh(?<algorithm>dsa|ecdsa|ed25519|rsa)key$},
31
+ %r{^ldom_(?<name>.*)$},
32
+ %r{^zone_(?<name>.*)_(?<attribute>brand|iptype|name|uuid|id|path|status)$}].freeze
33
+
34
+ # These facts have a one to one correlation between a legacy fact and a new
35
+ # structured fact.
36
+ EASY_FACTS = {
37
+ 'architecture' => "facts['os']['architecture']",
38
+ 'augeasversion' => "facts['augeas']['version']",
39
+ 'bios_release_date' => "facts['dmi']['bios']['release_date']",
40
+ 'bios_vendor' => "facts['dmi']['bios']['vendor']",
41
+ 'bios_version' => "facts['dmi']['bios']['version']",
42
+ 'boardassettag' => "facts['dmi']['board']['asset_tag']",
43
+ 'boardmanufacturer' => "facts['dmi']['board']['manufacturer']",
44
+ 'boardproductname' => "facts['dmi']['board']['product']",
45
+ 'boardserialnumber' => "facts['dmi']['board']['serial_number']",
46
+ 'chassisassettag' => "facts['dmi']['chassis']['asset_tag']",
47
+ 'chassistype' => "facts['dmi']['chassis']['type']",
48
+ 'domain' => "facts['networking']['domain']",
49
+ 'fqdn' => "facts['networking']['fqdn']",
50
+ 'gid' => "facts['identity']['group']",
51
+ 'hardwareisa' => "facts['processors']['isa']",
52
+ 'hardwaremodel' => "facts['os']['hardware']",
53
+ 'hostname' => "facts['networking']['hostname']",
54
+ 'id' => "facts['identity']['user']",
55
+ 'ipaddress' => "facts['networking']['ip']",
56
+ 'ipaddress6' => "facts['networking']['ip6']",
57
+ 'lsbdistcodename' => "facts['os']['distro']['codename']",
58
+ 'lsbdistdescription' => "facts['os']['distro']['description']",
59
+ 'lsbdistid' => "facts['os']['distro']['id']",
60
+ 'lsbdistrelease' => "facts['os']['distro']['release']['full']",
61
+ 'lsbmajdistrelease' => "facts['os']['distro']['release']['major']",
62
+ 'lsbminordistrelease' => "facts['os']['distro']['release']['minor']",
63
+ 'lsbrelease' => "facts['os']['distro']['release']['specification']",
64
+ 'macaddress' => "facts['networking']['mac']",
65
+ 'macosx_buildversion' => "facts['os']['build']",
66
+ 'macosx_productname' => "facts['os']['product']",
67
+ 'macosx_productversion' => "facts['os']['version']['full']",
68
+ 'macosx_productversion_major' => "facts['os']['version']['major']",
69
+ 'macosx_productversion_minor' => "facts['os']['version']['minor']",
70
+ 'manufacturer' => "facts['dmi']['manufacturer']",
71
+ 'memoryfree' => "facts['memory']['system']['available']",
72
+ 'memorysize' => "facts['memory']['system']['total']",
73
+ 'netmask' => "facts['networking']['netmask']",
74
+ 'netmask6' => "facts['networking']['netmask6']",
75
+ 'network' => "facts['networking']['network']",
76
+ 'network6' => "facts['networking']['network6']",
77
+ 'operatingsystem' => "facts['os']['name']",
78
+ 'operatingsystemmajrelease' => "facts['os']['release']['major']",
79
+ 'operatingsystemrelease' => "facts['os']['release']['full']",
80
+ 'osfamily' => "facts['os']['family']",
81
+ 'physicalprocessorcount' => "facts['processors']['physicalcount']",
82
+ 'processorcount' => "facts['processors']['count']",
83
+ 'productname' => "facts['dmi']['product']['name']",
84
+ 'rubyplatform' => "facts['ruby']['platform']",
85
+ 'rubysitedir' => "facts['ruby']['sitedir']",
86
+ 'rubyversion' => "facts['ruby']['version']",
87
+ 'selinux' => "facts['os']['selinux']['enabled']",
88
+ 'selinux_config_mode' => "facts['os']['selinux']['config_mode']",
89
+ 'selinux_config_policy' => "facts['os']['selinux']['config_policy']",
90
+ 'selinux_current_mode' => "facts['os']['selinux']['current_mode']",
91
+ 'selinux_enforced' => "facts['os']['selinux']['enforced']",
92
+ 'selinux_policyversion' => "facts['os']['selinux']['policy_version']",
93
+ 'serialnumber' => "facts['dmi']['product']['serial_number']",
94
+ 'swapencrypted' => "facts['memory']['swap']['encrypted']",
95
+ 'swapfree' => "facts['memory']['swap']['available']",
96
+ 'swapsize' => "facts['memory']['swap']['total']",
97
+ 'system32' => "facts['os']['windows']['system32']",
98
+ 'uptime' => "facts['system_uptime']['uptime']",
99
+ 'uptime_days' => "facts['system_uptime']['days']",
100
+ 'uptime_hours' => "facts['system_uptime']['hours']",
101
+ 'uptime_seconds' => "facts['system_uptime']['seconds']",
102
+ 'uuid' => "facts['dmi']['product']['uuid']",
103
+ 'xendomains' => "facts['xen']['domains']",
104
+ 'zonename' => "facts['solaris_zones']['current']",
105
+ }.freeze
106
+
107
+ # A list of valid hash key token types
108
+ HASH_KEY_TYPES = Set[
109
+ :STRING, # Double quoted string
110
+ :SSTRING, # Single quoted string
111
+ :NAME, # Unquoted single word
112
+ ].freeze
113
+
114
+ def check
115
+ tokens.select { |x| LEGACY_FACTS_VAR_TYPES.include?(x.type) }.each do |token|
116
+ fact_name = ''
117
+
118
+ # This matches legacy facts defined in the fact hash that use the top scope
119
+ # fact assignment.
120
+ if token.value.start_with?('::facts[')
121
+ fact_name = token.value.match(%r{::facts\['(.*)'\]})[1]
122
+
123
+ # This matches legacy facts defined in the fact hash.
124
+ elsif token.value.start_with?("facts['")
125
+ fact_name = token.value.match(%r{facts\['(.*)'\]})[1]
126
+
127
+ # This matches using legacy facts in a the new structured fact. For
128
+ # example this would match 'uuid' in $facts['uuid'] so it can be converted
129
+ # to facts['dmi']['product']['uuid']"
130
+ elsif token.value == 'facts'
131
+ fact_name = hash_key_for(token)
132
+
133
+ # Now we can get rid of top scopes. We don't need to
134
+ # preserve it because it won't work with the new structured facts.
135
+ elsif token.value.start_with?('::')
136
+ fact_name = token.value.sub(%r{^::}, '')
137
+ end
138
+
139
+ next unless EASY_FACTS.include?(fact_name) || UNCONVERTIBLE_FACTS.include?(fact_name) || fact_name.match(Regexp.union(REGEX_FACTS))
140
+ notify :warning, {
141
+ message: "legacy fact '#{fact_name}'",
142
+ line: token.line,
143
+ column: token.column,
144
+ token: token,
145
+ fact_name: fact_name,
146
+ }
147
+ end
148
+ end
149
+
150
+ # If the variable is using the $facts hash represented internally by multiple
151
+ # tokens, this helper simplifies accessing the hash key.
152
+ def hash_key_for(token)
153
+ lbrack_token = token.next_code_token
154
+ return '' unless lbrack_token && lbrack_token.type == :LBRACK
155
+
156
+ key_token = lbrack_token.next_code_token
157
+ return '' unless key_token && HASH_KEY_TYPES.include?(key_token.type)
158
+
159
+ key_token.value
160
+ end
161
+
162
+ def fix(problem)
163
+ fact_name = problem[:fact_name]
164
+
165
+ # Check if the variable is using the $facts hash represented internally by
166
+ # multiple tokens and remove the tokens for the old legacy key if so.
167
+ if problem[:token].value == 'facts'
168
+ loop do
169
+ t = problem[:token].next_token
170
+ remove_token(t)
171
+ break if t.type == :RBRACK
172
+ end
173
+ end
174
+
175
+ if EASY_FACTS.include?(fact_name)
176
+ problem[:token].value = EASY_FACTS[fact_name]
177
+ elsif fact_name.match(Regexp.union(REGEX_FACTS))
178
+ if (m = fact_name.match(%r{^blockdevice_(?<devicename>.*)_(?<attribute>model|size|vendor)$}))
179
+ problem[:token].value = "facts['disks']['" << m['devicename'] << "']['" << m['attribute'] << "']"
180
+ elsif (m = fact_name.match(%r{^(?<attribute>ipaddress|ipaddress6|macaddress|mtu|netmask|netmask6|network|network6)_(?<interface>.*)$}))
181
+ problem[:token].value = "facts['networking']['interfaces']['" << m['interface'] << "']['" << m['attribute'].sub('address', '') << "']"
182
+ elsif (m = fact_name.match(%r{^processor(?<id>[0-9]+)$}))
183
+ problem[:token].value = "facts['processors']['models'][" << m['id'] << ']'
184
+ elsif (m = fact_name.match(%r{^sp_(?<name>.*)$}))
185
+ problem[:token].value = "facts['system_profiler']['" << m['name'] << "']"
186
+ elsif (m = fact_name.match(%r{^ssh(?<algorithm>dsa|ecdsa|ed25519|rsa)key$}))
187
+ problem[:token].value = "facts['ssh']['" << m['algorithm'] << "']['key']"
188
+ elsif (m = fact_name.match(%r{^ldom_(?<name>.*)$}))
189
+ problem[:token].value = "facts['ldom']['" << m['name'] << "']"
190
+ elsif (m = fact_name.match(%r{^zone_(?<name>.*)_(?<attribute>brand|iptype|name|uuid|id|path|status)$}))
191
+ problem[:token].value = "facts['solaris_zones']['zones']['" << m['name'] << "']['" << m['attribute'] << "']"
192
+ end
193
+ end
194
+ end
195
+ end
@@ -0,0 +1,38 @@
1
+ # Public: A puppet-lint plugin that will check for the use of top scope facts.
2
+ # For example, the fact `$facts['kernel']` should be used over
3
+ # `$::kernel`.
4
+ #
5
+ # The check only finds facts using the top-scope: ie it will find $::operatingsystem
6
+ # but not $operatingsystem. It also all top scope variables are facts.
7
+ # If you have top scope variables that aren't facts you should configure the
8
+ # linter to ignore them.
9
+ #
10
+ # You can whitelist top scope variables to ignore via the Rake task.
11
+ # You should insert the following line to your Rakefile.
12
+ # `PuppetLint.configuration.top_scope_variables = ['location', 'role']`
13
+ #
14
+ # This plugin was adopted in to puppet-lint from https://github.com/mmckinst/puppet-lint-top_scope_facts-check
15
+ # Thanks to @mmckinst, @seanmil and @alexjfisher for the original work.
16
+ PuppetLint.new_check(:top_scope_facts) do
17
+ TOP_SCOPE_FACTS_VAR_TYPES = Set[:VARIABLE, :UNENC_VARIABLE]
18
+ def check
19
+ whitelist = ['trusted', 'facts'] + (PuppetLint.configuration.top_scope_variables || [])
20
+ whitelist = whitelist.join('|')
21
+ tokens.select { |x| TOP_SCOPE_FACTS_VAR_TYPES.include?(x.type) }.each do |token|
22
+ next unless %r{^::}.match?(token.value)
23
+ next if %r{^::(#{whitelist})\[?}.match?(token.value)
24
+ next if %r{^::[a-z0-9_][a-zA-Z0-9_]+::}.match?(token.value)
25
+
26
+ notify :warning, {
27
+ message: 'top scope fact instead of facts hash',
28
+ line: token.line,
29
+ column: token.column,
30
+ token: token,
31
+ }
32
+ end
33
+ end
34
+
35
+ def fix(problem)
36
+ problem[:token].value = "facts['" + problem[:token].value.sub(%r{^::}, '') + "']"
37
+ end
38
+ end
@@ -0,0 +1,48 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+ require 'json'
5
+
6
+ class PuppetLint::Report
7
+ # Formats problems and writes them to a file as a code climate compatible report.
8
+ class CodeClimateReporter
9
+ def self.write_report_file(problems, report_file)
10
+ report = []
11
+ problems.each do |messages|
12
+ messages.each do |message|
13
+ case message[:kind]
14
+ when :warning
15
+ severity = 'minor'
16
+ when :error
17
+ severity = 'major'
18
+ else
19
+ next
20
+ end
21
+
22
+ issue = {
23
+ type: :issue,
24
+ check_name: message[:check],
25
+ description: message[:message],
26
+ categories: [:Style],
27
+ severity: severity,
28
+ location: {
29
+ path: message[:path],
30
+ lines: {
31
+ begin: message[:line],
32
+ end: message[:line],
33
+ }
34
+ },
35
+ }
36
+
37
+ issue[:fingerprint] = Digest::MD5.hexdigest(Marshal.dump(issue))
38
+
39
+ if message.key?(:description) && message.key?(:help_uri)
40
+ issue[:content] = "#{message[:description].chomp('.')}. See [this page](#{message[:help_uri]}) for more information about the `#{message[:check]}` check."
41
+ end
42
+ report << issue
43
+ end
44
+ end
45
+ File.write(report_file, "#{JSON.pretty_generate(report)}\n")
46
+ end
47
+ end
48
+ end
@@ -4,6 +4,7 @@ require 'puppet-lint'
4
4
  require 'puppet-lint/optparser'
5
5
  require 'rake'
6
6
  require 'rake/tasklib'
7
+ require 'puppet-lint/report/codeclimate'
7
8
 
8
9
  # Public: A Rake task that can be loaded and used with everything you need.
9
10
  #
@@ -90,6 +91,7 @@ class PuppetLint::RakeTask < ::Rake::TaskLib
90
91
  RakeFileUtils.send(:verbose, true) do
91
92
  linter = PuppetLint.new
92
93
  matched_files = FileList[@pattern]
94
+ all_problems = []
93
95
 
94
96
  matched_files = matched_files.exclude(*@ignore_paths)
95
97
 
@@ -97,12 +99,17 @@ class PuppetLint::RakeTask < ::Rake::TaskLib
97
99
  next unless File.file?(puppet_file)
98
100
  linter.file = puppet_file
99
101
  linter.run
100
- linter.print_problems
102
+ all_problems << linter.print_problems
101
103
 
102
104
  if PuppetLint.configuration.fix && linter.problems.none? { |e| e[:check] == :syntax }
103
105
  IO.write(puppet_file, linter.manifest)
104
106
  end
105
107
  end
108
+
109
+ if PuppetLint.configuration.codeclimate_report_file
110
+ PuppetLint::Report::CodeClimateReporter.write_report_file(all_problems, PuppetLint.configuration.codeclimate_report_file)
111
+ end
112
+
106
113
  abort if linter.errors? || (
107
114
  linter.warnings? && PuppetLint.configuration.fail_on_warnings
108
115
  )
@@ -1,3 +1,3 @@
1
1
  class PuppetLint
2
- VERSION = '3.0.1'.freeze
2
+ VERSION = '3.2.0'.freeze
3
3
  end
data/lib/puppet-lint.rb CHANGED
@@ -124,6 +124,7 @@ class PuppetLint
124
124
  puts format % message
125
125
 
126
126
  puts " #{message[:reason]}" if message[:kind] == :ignored && !message[:reason].nil?
127
+ print_context(message)
127
128
  end
128
129
 
129
130
  # Internal: Format a problem message and print it to STDOUT so GitHub Actions
@@ -154,7 +155,8 @@ class PuppetLint
154
155
  def print_context(message)
155
156
  return if message[:check] == 'documentation'
156
157
  return if message[:kind] == :fixed
157
- line = get_context(message)
158
+ line = message[:context]
159
+ return unless line
158
160
  offset = line.index(%r{\S}) || 1
159
161
  puts "\n #{line.strip}"
160
162
  printf("%#{message[:column] + 2 - offset}s\n\n", '^')
@@ -168,6 +170,8 @@ class PuppetLint
168
170
  # Returns array of problem.
169
171
  def report(problems)
170
172
  json = []
173
+ print_stdout = !(configuration.json || configuration.sarif)
174
+
171
175
  problems.each do |message|
172
176
  next if message[:kind] == :ignored && !PuppetLint.configuration.show_ignored
173
177
 
@@ -175,12 +179,12 @@ class PuppetLint
175
179
 
176
180
  next unless message[:kind] == :fixed || [message[:kind], :all].include?(configuration.error_level)
177
181
 
178
- if configuration.json || configuration.sarif
179
- message['context'] = get_context(message) if configuration.with_context
180
- json << message
181
- else
182
+ message[:context] = get_context(message) if configuration.with_context
183
+
184
+ json << message
185
+
186
+ if print_stdout
182
187
  format_message(message)
183
- print_context(message) if configuration.with_context
184
188
  print_github_annotation(message) if configuration.github_actions
185
189
  end
186
190
  end
@@ -225,7 +229,7 @@ class PuppetLint
225
229
 
226
230
  # Public: Print any problems that were found out to stdout.
227
231
  #
228
- # Returns nothing.
232
+ # Returns an array of problems.
229
233
  def print_problems
230
234
  report(@problems)
231
235
  end
@@ -1,24 +1,22 @@
1
1
  ---
2
2
  require:
3
- - rubocop-performance
4
- - rubocop-rspec
3
+ - rubocop-performance
4
+ - rubocop-rspec
5
5
  AllCops:
6
+ TargetRubyVersion: '2.7'
6
7
  DisplayCopNames: true
7
- TargetRubyVersion: '2.6'
8
8
  SuggestExtensions: false
9
- Include:
10
- - "**/*.rb"
11
9
  Exclude:
12
- - bin/*
13
- - ".vendor/**/*"
14
- - "**/Gemfile"
15
- - "**/Rakefile"
16
- - pkg/**/*
17
- - spec/fixtures/**/*
18
- - vendor/**/*
19
- - "**/Puppetfile"
20
- - "**/Vagrantfile"
21
- - "**/Guardfile"
10
+ - "bin/*"
11
+ - ".vendor/**/*"
12
+ - "**/Gemfile"
13
+ - "**/Rakefile"
14
+ - "pkg/**/*"
15
+ - "spec/fixtures/**/*"
16
+ - "vendor/**/*"
17
+ - "**/Puppetfile"
18
+ - "**/Vagrantfile"
19
+ - "**/Guardfile"
22
20
  Layout/LineLength:
23
21
  Description: People have wide screens, use them.
24
22
  Max: 200
@@ -0,0 +1,38 @@
1
+ [
2
+ {
3
+ "type": "issue",
4
+ "check_name": "autoloader_layout",
5
+ "description": "test::foo not in autoload module layout",
6
+ "categories": [
7
+ "Style"
8
+ ],
9
+ "severity": "major",
10
+ "location": {
11
+ "path": "spec/fixtures/test/manifests/fail.pp",
12
+ "lines": {
13
+ "begin": 2,
14
+ "end": 2
15
+ }
16
+ },
17
+ "fingerprint": "f12bce7a776454ab9ffac2d20dcc34ba",
18
+ "content": "Test the manifest tokens for any classes or defined types that are not in an appropriately named file for the autoloader to detect and record an error of each instance found. See [this page](https://puppet.com/docs/puppet/latest/style_guide.html#separate-files) for more information about the `autoloader_layout` check."
19
+ },
20
+ {
21
+ "type": "issue",
22
+ "check_name": "parameter_order",
23
+ "description": "optional parameter listed before required parameter",
24
+ "categories": [
25
+ "Style"
26
+ ],
27
+ "severity": "minor",
28
+ "location": {
29
+ "path": "spec/fixtures/test/manifests/warning.pp",
30
+ "lines": {
31
+ "begin": 2,
32
+ "end": 2
33
+ }
34
+ },
35
+ "fingerprint": "e34cf289e008446b633d1be7cf58120e",
36
+ "content": "Test the manifest tokens for any parameterised classes or defined types that take parameters and record a warning if there are any optional parameters listed before required parameters. See [this page](https://puppet.com/docs/puppet/latest/style_guide.html#display-order-of-parameters) for more information about the `parameter_order` check."
37
+ }
38
+ ]
@@ -378,7 +378,7 @@ describe PuppetLint::Bin do
378
378
  if respond_to?(:include_json)
379
379
  is_expected.to include_json([[{ 'KIND' => 'WARNING' }]])
380
380
  else
381
- is_expected.to match(%r{\[\n \{})
381
+ is_expected.to match(%r{\[\n \[\n \{})
382
382
  end
383
383
  end
384
384
  end
@@ -397,7 +397,7 @@ describe PuppetLint::Bin do
397
397
  if respond_to?(:include_json)
398
398
  is_expected.to include_json([[{ 'KIND' => 'ERROR' }], [{ 'KIND' => 'WARNING' }]])
399
399
  else
400
- is_expected.to match(%r{\[\n \{})
400
+ is_expected.to match(%r{\[\n \[\n \{})
401
401
  end
402
402
  end
403
403
  end
@@ -437,6 +437,30 @@ describe PuppetLint::Bin do
437
437
  end
438
438
  end
439
439
 
440
+ context 'when outputting code climate report' do
441
+ let(:report_file) do
442
+ Tempfile.new('report_file.json')
443
+ end
444
+
445
+ let(:args) do
446
+ [
447
+ '--codeclimate-report-file',
448
+ report_file.path,
449
+ 'spec/fixtures/test/manifests/fail.pp',
450
+ 'spec/fixtures/test/manifests/warning.pp',
451
+ ]
452
+ end
453
+
454
+ after(:each) do
455
+ report_file.unlink
456
+ end
457
+
458
+ it 'creates a code climate report' do
459
+ expect(bin.exitstatus).to eq(1)
460
+ expect(FileUtils.compare_file(report_file.path, 'spec/fixtures/test/reports/code_climate.json')).to be_truthy
461
+ end
462
+ end
463
+
440
464
  context 'when hiding ignored problems' do
441
465
  let(:args) do
442
466
  [