openvox-lint 1.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.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +52 -0
  3. data/DOCUMENTATION.md +481 -0
  4. data/LICENSE +83 -0
  5. data/README.md +497 -0
  6. data/bin/openvox-lint +7 -0
  7. data/lib/openvox-lint/check_plugin.rb +147 -0
  8. data/lib/openvox-lint/checks.rb +46 -0
  9. data/lib/openvox-lint/cli.rb +87 -0
  10. data/lib/openvox-lint/configuration.rb +59 -0
  11. data/lib/openvox-lint/lexer.rb +342 -0
  12. data/lib/openvox-lint/linter.rb +72 -0
  13. data/lib/openvox-lint/plugins/checks/arrow_alignment.rb +42 -0
  14. data/lib/openvox-lint/plugins/checks/autoloader_layout.rb +31 -0
  15. data/lib/openvox-lint/plugins/checks/case_without_default.rb +28 -0
  16. data/lib/openvox-lint/plugins/checks/class_inherits_params.rb +13 -0
  17. data/lib/openvox-lint/plugins/checks/documentation.rb +26 -0
  18. data/lib/openvox-lint/plugins/checks/double_quoted_strings.rb +19 -0
  19. data/lib/openvox-lint/plugins/checks/duplicate_params.rb +24 -0
  20. data/lib/openvox-lint/plugins/checks/ensure_first_param.rb +28 -0
  21. data/lib/openvox-lint/plugins/checks/ensure_not_symlink_target.rb +29 -0
  22. data/lib/openvox-lint/plugins/checks/file_mode.rb +33 -0
  23. data/lib/openvox-lint/plugins/checks/hard_tabs.rb +15 -0
  24. data/lib/openvox-lint/plugins/checks/hiera3_function.rb +16 -0
  25. data/lib/openvox-lint/plugins/checks/import_statement.rb +13 -0
  26. data/lib/openvox-lint/plugins/checks/inherits_across_namespaces.rb +27 -0
  27. data/lib/openvox-lint/plugins/checks/leading_zero.rb +22 -0
  28. data/lib/openvox-lint/plugins/checks/legacy_facts.rb +47 -0
  29. data/lib/openvox-lint/plugins/checks/line_length.rb +18 -0
  30. data/lib/openvox-lint/plugins/checks/nested_classes_or_defines.rb +26 -0
  31. data/lib/openvox-lint/plugins/checks/node_name_unquoted.rb +18 -0
  32. data/lib/openvox-lint/plugins/checks/only_variable_string.rb +18 -0
  33. data/lib/openvox-lint/plugins/checks/parameter_order.rb +25 -0
  34. data/lib/openvox-lint/plugins/checks/puppet_url_without_modules.rb +17 -0
  35. data/lib/openvox-lint/plugins/checks/quoted_booleans.rb +16 -0
  36. data/lib/openvox-lint/plugins/checks/relative_classname_inclusion.rb +24 -0
  37. data/lib/openvox-lint/plugins/checks/resource_reference_without_title_capital.rb +21 -0
  38. data/lib/openvox-lint/plugins/checks/selector_inside_resource.rb +15 -0
  39. data/lib/openvox-lint/plugins/checks/single_quote_string_with_variables.rb +16 -0
  40. data/lib/openvox-lint/plugins/checks/space_before_arrow.rb +20 -0
  41. data/lib/openvox-lint/plugins/checks/star_comments.rb +13 -0
  42. data/lib/openvox-lint/plugins/checks/strict_indent.rb +16 -0
  43. data/lib/openvox-lint/plugins/checks/top_scope_facts.rb +19 -0
  44. data/lib/openvox-lint/plugins/checks/trailing_comma.rb +24 -0
  45. data/lib/openvox-lint/plugins/checks/trailing_whitespace.rb +14 -0
  46. data/lib/openvox-lint/plugins/checks/unquoted_file_mode.rb +24 -0
  47. data/lib/openvox-lint/plugins/checks/unquoted_resource_title.rb +13 -0
  48. data/lib/openvox-lint/plugins/checks/variable_contains_dash.rb +15 -0
  49. data/lib/openvox-lint/plugins/checks/variable_is_lowercase.rb +16 -0
  50. data/lib/openvox-lint/plugins/checks/variables_not_enclosed.rb +19 -0
  51. data/lib/openvox-lint/report.rb +86 -0
  52. data/lib/openvox-lint/token.rb +38 -0
  53. data/lib/openvox-lint/version.rb +5 -0
  54. data/lib/openvox-lint.rb +47 -0
  55. metadata +145 -0
@@ -0,0 +1,24 @@
1
+ # frozen_string_literal: true
2
+
3
+ # File modes should be quoted strings.
4
+ OpenvoxLint.new_check(:unquoted_file_mode) do
5
+ def check
6
+ tokens.each_with_index do |tok, i|
7
+ next unless tok.type == :NAME && tok.value == 'mode'
8
+ arrow = next_non_ws(i + 1)
9
+ next unless arrow && arrow.type == :FARROW
10
+ val = next_non_ws(tokens.index(arrow) + 1)
11
+ next unless val && val.type == :NUMBER
12
+ notify :warning,
13
+ message: 'unquoted file mode',
14
+ line: val.line, column: val.column
15
+ end
16
+ end
17
+
18
+ private
19
+
20
+ def next_non_ws(start)
21
+ (start...tokens.length).each { |j| return tokens[j] unless tokens[j].formatting? }
22
+ nil
23
+ end
24
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Resource titles should be quoted.
4
+ OpenvoxLint.new_check(:unquoted_resource_title) do
5
+ def check
6
+ title_tokens.each do |tok|
7
+ next unless tok.type == :NAME
8
+ notify :warning,
9
+ message: "unquoted resource title '#{tok.value}'",
10
+ line: tok.line, column: tok.column
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Variable names should not contain dashes.
4
+ OpenvoxLint.new_check(:variable_contains_dash) do
5
+ def check
6
+ tokens.each do |tok|
7
+ next unless tok.type == :VARIABLE
8
+ next unless tok.value.include?('-')
9
+ notify :warning,
10
+ message: "variable '#{tok.value}' contains a dash",
11
+ line: tok.line,
12
+ column: tok.column
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # All variable names must be lowercase.
4
+ OpenvoxLint.new_check(:variable_is_lowercase) do
5
+ def check
6
+ tokens.each do |tok|
7
+ next unless tok.type == :VARIABLE
8
+ name = tok.value.sub(/^\$/, '')
9
+ next if name =~ /\A[a-z_][a-z0-9_:]*\z/ || name.empty?
10
+ notify :warning,
11
+ message: "variable '#{tok.value}' contains uppercase characters",
12
+ line: tok.line,
13
+ column: tok.column
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Variables in double-quoted strings should be enclosed in braces.
4
+ # e.g. "$foo" should be "${foo}"
5
+ OpenvoxLint.new_check(:variables_not_enclosed) do
6
+ def check
7
+ tokens.each do |tok|
8
+ next unless tok.type == :DQSTRING || tok.type == :STRING
9
+ val = tok.value
10
+ # Look for $var not followed by { inside double-quoted strings
11
+ next unless val =~ /\$([a-zA-Z_][a-zA-Z0-9_:]*)/
12
+ next if val =~ /\$\{/ # already enclosed
13
+ notify :warning,
14
+ message: 'variable not enclosed in braces within string',
15
+ line: tok.line,
16
+ column: tok.column
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'json'
4
+
5
+ module OpenvoxLint
6
+ # Formats and outputs lint problems in the requested format.
7
+ class Report
8
+ SEVERITIES = { warning: 'WARNING', error: 'ERROR' }.freeze
9
+
10
+ def initialize(configuration)
11
+ @config = configuration
12
+ end
13
+
14
+ def format(problems, io: $stdout)
15
+ case @config.log_format
16
+ when 'json' then format_json(problems, io)
17
+ when 'csv' then format_csv(problems, io)
18
+ when 'github' then format_github(problems, io)
19
+ when 'codeclimate' then format_codeclimate(problems, io)
20
+ when 'custom' then format_custom(problems, io)
21
+ else format_text(problems, io)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def format_text(problems, io)
28
+ problems.each do |p|
29
+ severity = SEVERITIES[p[:kind]] || p[:kind].to_s.upcase
30
+ parts = []
31
+ parts << p[:path] if @config.with_filename
32
+ parts << p[:line].to_s
33
+ parts << p[:column].to_s if @config.column && p[:column]
34
+ io.puts "#{parts.join(':')}: #{severity}: #{p[:check]}: #{p[:message]}"
35
+ end
36
+ end
37
+
38
+ def format_json(problems, io)
39
+ io.puts JSON.pretty_generate(problems.map { |p| serialise(p) })
40
+ end
41
+
42
+ def format_csv(problems, io)
43
+ io.puts 'path,line,column,kind,check,message'
44
+ problems.each do |p|
45
+ io.puts [p[:path], p[:line], p[:column], p[:kind], p[:check],
46
+ %("#{p[:message]}")].join(',')
47
+ end
48
+ end
49
+
50
+ def format_github(problems, io)
51
+ problems.each do |p|
52
+ kind = p[:kind] == :error ? 'error' : 'warning'
53
+ io.puts "::#{kind} file=#{p[:path]},line=#{p[:line]},col=#{p[:column]}::#{p[:check]}: #{p[:message]}"
54
+ end
55
+ end
56
+
57
+ def format_codeclimate(problems, io)
58
+ issues = problems.map do |p|
59
+ {
60
+ type: 'issue', check_name: p[:check].to_s,
61
+ description: p[:message], categories: ['Style'],
62
+ severity: p[:kind] == :error ? 'major' : 'minor',
63
+ location: { path: p[:path], lines: { begin: p[:line], end: p[:line] } },
64
+ }
65
+ end
66
+ io.puts JSON.pretty_generate(issues)
67
+ end
68
+
69
+ def format_custom(problems, io)
70
+ fmt = @config.custom_log_format
71
+ problems.each do |p|
72
+ line = fmt.dup
73
+ { '%{path}' => p[:path], '%{line}' => p[:line], '%{column}' => p[:column],
74
+ '%{KIND}' => SEVERITIES[p[:kind]] || p[:kind].to_s.upcase,
75
+ '%{kind}' => p[:kind], '%{check}' => p[:check],
76
+ '%{message}' => p[:message] }.each { |k, v| line.gsub!(k, v.to_s) }
77
+ io.puts line
78
+ end
79
+ end
80
+
81
+ def serialise(p)
82
+ { path: p[:path], line: p[:line], column: p[:column],
83
+ kind: p[:kind].to_s, check: p[:check].to_s, message: p[:message] }
84
+ end
85
+ end
86
+ end
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenvoxLint
4
+ # Represents a single token produced by the lexer.
5
+ #
6
+ # Each token has a type (Symbol), a raw value (String), and positional
7
+ # metadata (line, column). Tokens are linked together in a doubly-linked
8
+ # list via +prev_token+ / +next_token+ so that check plugins can easily
9
+ # navigate the stream.
10
+ class Token
11
+ attr_accessor :type, :value, :line, :column,
12
+ :prev_token, :next_token
13
+
14
+ def initialize(type, value, line, column)
15
+ @type = type
16
+ @value = value
17
+ @line = line
18
+ @column = column
19
+ end
20
+
21
+ def to_s
22
+ "#{type}(#{value.inspect}) @ #{line}:#{column}"
23
+ end
24
+
25
+ def inspect
26
+ "#<OpenvoxLint::Token #{self}>"
27
+ end
28
+
29
+ # Convenience: is this token a formatting/whitespace token?
30
+ def formatting?
31
+ FORMATTING_TYPES.include?(type)
32
+ end
33
+
34
+ FORMATTING_TYPES = %i[
35
+ WHITESPACE INDENT NEWLINE COMMENT MLCOMMENT SLASH_COMMENT
36
+ ].freeze
37
+ end
38
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OpenvoxLint
4
+ VERSION = '1.0.0'
5
+ end
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'openvox-lint/version'
4
+ require_relative 'openvox-lint/configuration'
5
+ require_relative 'openvox-lint/token'
6
+ require_relative 'openvox-lint/lexer'
7
+ require_relative 'openvox-lint/check_plugin'
8
+ require_relative 'openvox-lint/checks'
9
+ require_relative 'openvox-lint/report'
10
+ require_relative 'openvox-lint/linter'
11
+ require_relative 'openvox-lint/cli'
12
+
13
+ # OpenvoxLint – a style-guide linter for OpenVox / Puppet manifests.
14
+ #
15
+ # Compatible with:
16
+ # - OpenVox 8.x (community fork of Puppet)
17
+ # - Puppet 8.x
18
+ # - Legacy Puppet 7.x manifests (with deprecation warnings)
19
+ module OpenvoxLint
20
+ class Error < StandardError; end
21
+ class NoFix < StandardError; end
22
+
23
+ class << self
24
+ def configuration
25
+ @configuration ||= Configuration.new
26
+ end
27
+
28
+ def configure
29
+ yield(configuration)
30
+ end
31
+
32
+ def checks
33
+ @checks ||= {}
34
+ end
35
+
36
+ def new_check(name, &block)
37
+ klass = Class.new(CheckPlugin, &block)
38
+ klass.instance_variable_set(:@check_name, name)
39
+ checks[name] = klass
40
+ end
41
+ end
42
+ end
43
+
44
+ # Auto-load every built-in check plugin.
45
+ Dir[File.join(__dir__, 'openvox-lint', 'plugins', 'checks', '*.rb')].sort.each do |f|
46
+ require f
47
+ end
metadata ADDED
@@ -0,0 +1,145 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: openvox-lint
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ platform: ruby
6
+ authors:
7
+ - Johnny Sheets
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 2026-02-12 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: rake
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '13.0'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '13.0'
26
+ - !ruby/object:Gem::Dependency
27
+ name: rspec
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - "~>"
31
+ - !ruby/object:Gem::Version
32
+ version: '3.12'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - "~>"
38
+ - !ruby/object:Gem::Version
39
+ version: '3.12'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rubocop
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - "~>"
45
+ - !ruby/object:Gem::Version
46
+ version: '1.50'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - "~>"
52
+ - !ruby/object:Gem::Version
53
+ version: '1.50'
54
+ description: |
55
+ openvox-lint is a linter for OpenVox and Puppet manifests. It checks your
56
+ code against the Puppet style guide and catches common errors including
57
+ deprecated patterns, legacy facts, strict mode violations, and Puppet 8+
58
+ language issues. Fully compatible with OpenVox 8.x and Puppet 8.x.
59
+ email:
60
+ - jsheets@xai.com
61
+ executables:
62
+ - openvox-lint
63
+ extensions: []
64
+ extra_rdoc_files: []
65
+ files:
66
+ - CHANGELOG.md
67
+ - DOCUMENTATION.md
68
+ - LICENSE
69
+ - README.md
70
+ - bin/openvox-lint
71
+ - lib/openvox-lint.rb
72
+ - lib/openvox-lint/check_plugin.rb
73
+ - lib/openvox-lint/checks.rb
74
+ - lib/openvox-lint/cli.rb
75
+ - lib/openvox-lint/configuration.rb
76
+ - lib/openvox-lint/lexer.rb
77
+ - lib/openvox-lint/linter.rb
78
+ - lib/openvox-lint/plugins/checks/arrow_alignment.rb
79
+ - lib/openvox-lint/plugins/checks/autoloader_layout.rb
80
+ - lib/openvox-lint/plugins/checks/case_without_default.rb
81
+ - lib/openvox-lint/plugins/checks/class_inherits_params.rb
82
+ - lib/openvox-lint/plugins/checks/documentation.rb
83
+ - lib/openvox-lint/plugins/checks/double_quoted_strings.rb
84
+ - lib/openvox-lint/plugins/checks/duplicate_params.rb
85
+ - lib/openvox-lint/plugins/checks/ensure_first_param.rb
86
+ - lib/openvox-lint/plugins/checks/ensure_not_symlink_target.rb
87
+ - lib/openvox-lint/plugins/checks/file_mode.rb
88
+ - lib/openvox-lint/plugins/checks/hard_tabs.rb
89
+ - lib/openvox-lint/plugins/checks/hiera3_function.rb
90
+ - lib/openvox-lint/plugins/checks/import_statement.rb
91
+ - lib/openvox-lint/plugins/checks/inherits_across_namespaces.rb
92
+ - lib/openvox-lint/plugins/checks/leading_zero.rb
93
+ - lib/openvox-lint/plugins/checks/legacy_facts.rb
94
+ - lib/openvox-lint/plugins/checks/line_length.rb
95
+ - lib/openvox-lint/plugins/checks/nested_classes_or_defines.rb
96
+ - lib/openvox-lint/plugins/checks/node_name_unquoted.rb
97
+ - lib/openvox-lint/plugins/checks/only_variable_string.rb
98
+ - lib/openvox-lint/plugins/checks/parameter_order.rb
99
+ - lib/openvox-lint/plugins/checks/puppet_url_without_modules.rb
100
+ - lib/openvox-lint/plugins/checks/quoted_booleans.rb
101
+ - lib/openvox-lint/plugins/checks/relative_classname_inclusion.rb
102
+ - lib/openvox-lint/plugins/checks/resource_reference_without_title_capital.rb
103
+ - lib/openvox-lint/plugins/checks/selector_inside_resource.rb
104
+ - lib/openvox-lint/plugins/checks/single_quote_string_with_variables.rb
105
+ - lib/openvox-lint/plugins/checks/space_before_arrow.rb
106
+ - lib/openvox-lint/plugins/checks/star_comments.rb
107
+ - lib/openvox-lint/plugins/checks/strict_indent.rb
108
+ - lib/openvox-lint/plugins/checks/top_scope_facts.rb
109
+ - lib/openvox-lint/plugins/checks/trailing_comma.rb
110
+ - lib/openvox-lint/plugins/checks/trailing_whitespace.rb
111
+ - lib/openvox-lint/plugins/checks/unquoted_file_mode.rb
112
+ - lib/openvox-lint/plugins/checks/unquoted_resource_title.rb
113
+ - lib/openvox-lint/plugins/checks/variable_contains_dash.rb
114
+ - lib/openvox-lint/plugins/checks/variable_is_lowercase.rb
115
+ - lib/openvox-lint/plugins/checks/variables_not_enclosed.rb
116
+ - lib/openvox-lint/report.rb
117
+ - lib/openvox-lint/token.rb
118
+ - lib/openvox-lint/version.rb
119
+ homepage: https://github.com/cvquesty/openvox-lint
120
+ licenses:
121
+ - Apache-2.0
122
+ metadata:
123
+ homepage_uri: https://github.com/cvquesty/openvox-lint
124
+ source_code_uri: https://github.com/cvquesty/openvox-lint
125
+ bug_tracker_uri: https://github.com/cvquesty/openvox-lint/issues
126
+ changelog_uri: https://github.com/cvquesty/openvox-lint/blob/main/CHANGELOG.md
127
+ rubygems_mfa_required: 'true'
128
+ rdoc_options: []
129
+ require_paths:
130
+ - lib
131
+ required_ruby_version: !ruby/object:Gem::Requirement
132
+ requirements:
133
+ - - ">="
134
+ - !ruby/object:Gem::Version
135
+ version: 3.1.0
136
+ required_rubygems_version: !ruby/object:Gem::Requirement
137
+ requirements:
138
+ - - ">="
139
+ - !ruby/object:Gem::Version
140
+ version: '0'
141
+ requirements: []
142
+ rubygems_version: 3.6.3
143
+ specification_version: 4
144
+ summary: Check OpenVox/Puppet manifests against the style guide
145
+ test_files: []