openvox-lint 1.0.7 → 1.0.8
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/CHANGELOG.md +77 -0
- data/DOCUMENTATION.md +20 -20
- data/README.md +8 -1
- data/lib/openvox-lint/check_plugin.rb +2 -1
- data/lib/openvox-lint/checks.rb +47 -3
- data/lib/openvox-lint/cli.rb +13 -4
- data/lib/openvox-lint/configuration.rb +2 -3
- data/lib/openvox-lint/lexer.rb +23 -9
- data/lib/openvox-lint/linter.rb +7 -1
- data/lib/openvox-lint/plugins/checks/arrow_alignment.rb +38 -0
- data/lib/openvox-lint/plugins/checks/duplicate_params.rb +2 -2
- data/lib/openvox-lint/plugins/checks/legacy_facts.rb +3 -1
- data/lib/openvox-lint/plugins/checks/parameter_order.rb +2 -2
- data/lib/openvox-lint/plugins/checks/resource_reference_without_title_capital.rb +17 -2
- data/lib/openvox-lint/plugins/checks/top_scope_facts.rb +3 -1
- data/lib/openvox-lint/plugins/checks/trailing_comma.rb +20 -14
- data/lib/openvox-lint/plugins/checks/variables_not_enclosed.rb +13 -7
- data/lib/openvox-lint/version.rb +1 -1
- data/lib/openvox-lint.rb +10 -0
- metadata +6 -6
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: abe996811c55331e6e81030b3b421c822f152e7e208767c0ba8154e8d6dfe628
|
|
4
|
+
data.tar.gz: bbd33e6c54ac940eb8b1a3e64ef511ef7ab6412282c12f48e3739508c3d9222c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b1e8359096a9156ef02eb2d18dd5af1171593d0f4c47a1957827a043212185dabd09608786c1df606257ca3b21a183978c084d289c7550f900e863367f8697b
|
|
7
|
+
data.tar.gz: 54bb7aba3f2afef0d6ba51f0346a8f05b7bf495a944a93af14e89144c2d28008ff09243e469b9330cc715a6200c42ea9172a83cebac57164e21088f3c9117c25
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,83 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to openvox-lint will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.0.8] - 2026-03-04
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- **CLI: stale configuration on re-invocation**: The global configuration
|
|
10
|
+
singleton is now reset at the start of every `CLI#run`. Repeated
|
|
11
|
+
invocations in the same Ruby process (Vim plugins, guard, Rake loops)
|
|
12
|
+
no longer inherit stale `disabled_checks`, `only_checks`, or other
|
|
13
|
+
settings from a previous run.
|
|
14
|
+
|
|
15
|
+
- **CLI: `--config` flag ignored when local RC exists**: The `-c` /
|
|
16
|
+
`--config FILE` flag now takes strict priority. Previously a local
|
|
17
|
+
`.openvox-lint.rc` would shadow an explicit `--config` path.
|
|
18
|
+
|
|
19
|
+
- **lint:ignore / lint:endignore block suppression**: Block-style ignore
|
|
20
|
+
comments now work as documented. `# lint:ignore:check_name` on its
|
|
21
|
+
own line opens a suppression block; `# lint:endignore` closes it.
|
|
22
|
+
All problems on lines between the two comments are suppressed for
|
|
23
|
+
the named checks. Inline ignore comments (on the same line as code)
|
|
24
|
+
continue to work as before.
|
|
25
|
+
|
|
26
|
+
- **ignore-paths glob matching with relative/absolute prefixes**: The
|
|
27
|
+
`--ignore-paths` glob patterns (and the defaults `vendor/**/*.pp`,
|
|
28
|
+
`pkg/**/*.pp`, `spec/**/*.pp`) now match correctly regardless of
|
|
29
|
+
whether the file was discovered via `./vendor/foo.pp`, `vendor/foo.pp`,
|
|
30
|
+
or an absolute path.
|
|
31
|
+
|
|
32
|
+
- **trailing_comma false positives on non-resource braces**: The
|
|
33
|
+
`trailing_comma` check now only fires inside resource bodies
|
|
34
|
+
(`name { ... }`). It no longer produces false positives on `if`,
|
|
35
|
+
`unless`, `case`, class bodies, or other brace-delimited contexts
|
|
36
|
+
where a trailing comma is not expected.
|
|
37
|
+
|
|
38
|
+
- **arrow_alignment / space_before_arrow contradictory warnings**: The
|
|
39
|
+
`arrow_alignment` check now defers to `space_before_arrow` when the
|
|
40
|
+
misalignment in a group is caused by the longest key having extra
|
|
41
|
+
whitespace. This eliminates contradictory double-warnings on the
|
|
42
|
+
same resource block.
|
|
43
|
+
|
|
44
|
+
- **Unterminated strings and regex now reported as errors**: The lexer
|
|
45
|
+
now raises `OpenvoxLint::Error` when a single-quoted string,
|
|
46
|
+
double-quoted string, or regex literal is not terminated before
|
|
47
|
+
end-of-file. Previously the lexer silently produced a truncated
|
|
48
|
+
token covering the rest of the file, leading to wrong lint results.
|
|
49
|
+
|
|
50
|
+
- **Multi-line string tokens now report correct line number**: String
|
|
51
|
+
tokens that span multiple lines now record the **starting** line
|
|
52
|
+
number, not the ending line. Checks that flag these tokens now
|
|
53
|
+
point to the correct source location.
|
|
54
|
+
|
|
55
|
+
- **variables_not_enclosed mixed variable handling**: Strings containing
|
|
56
|
+
both enclosed (`${bar}`) and unenclosed (`$foo`) variables now
|
|
57
|
+
correctly flag only the unenclosed references. Previously the
|
|
58
|
+
entire string was skipped if any `${...}` pattern was present.
|
|
59
|
+
|
|
60
|
+
- **resource_reference_without_title_capital expanded allowlist**: The
|
|
61
|
+
function allowlist that prevents false positives on `name[...]`
|
|
62
|
+
patterns now covers 40+ Puppet built-in and stdlib functions
|
|
63
|
+
(`each`, `map`, `filter`, `reduce`, `lookup`, `dig`, etc.),
|
|
64
|
+
not just the original 5.
|
|
65
|
+
|
|
66
|
+
### Changed
|
|
67
|
+
|
|
68
|
+
- **duplicate_params / parameter_order: removed dead code**: Both checks
|
|
69
|
+
contained `.formatting?` guard clauses that could never trigger
|
|
70
|
+
because they operate on pre-filtered semantic tokens. The dead code
|
|
71
|
+
has been removed for clarity.
|
|
72
|
+
|
|
73
|
+
- **Check registry duplicate warning**: `OpenvoxLint.new_check` now
|
|
74
|
+
emits a warning to stderr (when `OPENVOX_LINT_DEBUG` is set) if a
|
|
75
|
+
check name that is already registered is overwritten. This helps
|
|
76
|
+
detect accidental name collisions in custom check plugins.
|
|
77
|
+
|
|
78
|
+
- **Gemfile cleaned up**: Removed duplicate gem declarations that were
|
|
79
|
+
listed in both the Gemfile `group` block and the gemspec
|
|
80
|
+
`add_development_dependency` entries.
|
|
81
|
+
|
|
5
82
|
## [1.0.7] - 2026-02-25
|
|
6
83
|
|
|
7
84
|
### Changed
|
data/DOCUMENTATION.md
CHANGED
|
@@ -46,7 +46,7 @@ the lexer token types, the plugin system, and integration guidance.
|
|
|
46
46
|
|
|
47
47
|
| Constant | Value | Description |
|
|
48
48
|
|----------|-------|-------------|
|
|
49
|
-
| `VERSION` | `'1.0.
|
|
49
|
+
| `VERSION` | `'1.0.8'` | Gem version |
|
|
50
50
|
|
|
51
51
|
### Class Methods
|
|
52
52
|
|
|
@@ -54,8 +54,9 @@ the lexer token types, the plugin system, and integration guidance.
|
|
|
54
54
|
|--------|---------|-------------|
|
|
55
55
|
| `.configuration` | `Configuration` | Global configuration singleton |
|
|
56
56
|
| `.configure { \|c\| }` | `Configuration` | Yields configuration for block-style setup |
|
|
57
|
+
| `.reset_configuration!` | `Configuration` | Reset configuration to defaults (called by CLI) |
|
|
57
58
|
| `.checks` | `Hash{Symbol => Class}` | Registry of loaded check classes |
|
|
58
|
-
| `.new_check(name, &block)` | `Class` | Register a new check plugin |
|
|
59
|
+
| `.new_check(name, &block)` | `Class` | Register a new check plugin (warns on duplicates) |
|
|
59
60
|
|
|
60
61
|
### Exceptions
|
|
61
62
|
|
|
@@ -248,7 +249,6 @@ notify :warning, # or :error
|
|
|
248
249
|
| `only_checks` | `Array<Symbol>` | `[]` | Run only these checks |
|
|
249
250
|
| `disabled_checks` | `Array<Symbol>` | `[]` | Skip these checks |
|
|
250
251
|
| `ignore_paths` | `Array<String>` | vendor, pkg, spec | Glob patterns to ignore |
|
|
251
|
-
| `config_file` | `String` | `.openvox-lint.rc` | RC file path |
|
|
252
252
|
| `relative` | `Boolean` | `false` | Use relative paths |
|
|
253
253
|
| `column` | `Boolean` | `true` | Show column numbers |
|
|
254
254
|
| `custom_log_format` | `String\|nil` | `nil` | Custom format string |
|
|
@@ -517,20 +517,20 @@ Place the check file in `lib/openvox-lint/plugins/checks/my_check.rb`.
|
|
|
517
517
|
|
|
518
518
|
## File Inventory
|
|
519
519
|
|
|
520
|
-
| File |
|
|
521
|
-
|
|
522
|
-
| `bin/openvox-lint` |
|
|
523
|
-
| `lib/openvox-lint.rb` |
|
|
524
|
-
| `lib/openvox-lint/version.rb` |
|
|
525
|
-
| `lib/openvox-lint/configuration.rb` |
|
|
526
|
-
| `lib/openvox-lint/token.rb` |
|
|
527
|
-
| `lib/openvox-lint/lexer.rb` |
|
|
528
|
-
| `lib/openvox-lint/check_plugin.rb` |
|
|
529
|
-
| `lib/openvox-lint/checks.rb` |
|
|
530
|
-
| `lib/openvox-lint/report.rb` |
|
|
531
|
-
| `lib/openvox-lint/linter.rb` |
|
|
532
|
-
| `lib/openvox-lint/cli.rb` |
|
|
533
|
-
| `lib/openvox-lint/plugins/checks/*.rb` | 38
|
|
534
|
-
| `spec/spec_helper.rb` |
|
|
535
|
-
| `spec/unit/lexer_spec.rb` |
|
|
536
|
-
| `spec/unit/checks_spec.rb` |
|
|
520
|
+
| File | Description |
|
|
521
|
+
|------|-------------|
|
|
522
|
+
| `bin/openvox-lint` | CLI entry point |
|
|
523
|
+
| `lib/openvox-lint.rb` | Main module, auto-loader |
|
|
524
|
+
| `lib/openvox-lint/version.rb` | Version constant |
|
|
525
|
+
| `lib/openvox-lint/configuration.rb` | Configuration management |
|
|
526
|
+
| `lib/openvox-lint/token.rb` | Token data structure |
|
|
527
|
+
| `lib/openvox-lint/lexer.rb` | Puppet/OpenVox lexer |
|
|
528
|
+
| `lib/openvox-lint/check_plugin.rb` | Base check class |
|
|
529
|
+
| `lib/openvox-lint/checks.rb` | Check runner with lint:ignore support |
|
|
530
|
+
| `lib/openvox-lint/report.rb` | Output formatters |
|
|
531
|
+
| `lib/openvox-lint/linter.rb` | File orchestrator |
|
|
532
|
+
| `lib/openvox-lint/cli.rb` | CLI parser |
|
|
533
|
+
| `lib/openvox-lint/plugins/checks/*.rb` | 38 built-in check plugins |
|
|
534
|
+
| `spec/spec_helper.rb` | Test helper |
|
|
535
|
+
| `spec/unit/lexer_spec.rb` | Lexer tests |
|
|
536
|
+
| `spec/unit/checks_spec.rb` | Check tests |
|
data/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# openvox-lint
|
|
2
2
|
|
|
3
|
-
](https://rubygems.org/gems/openvox-lint)
|
|
4
|
+
[](https://rubygems.org/gems/openvox-lint)
|
|
4
5
|

|
|
5
6
|

|
|
6
7
|

|
|
@@ -637,6 +638,12 @@ openvox-lint/
|
|
|
637
638
|
|
|
638
639
|
---
|
|
639
640
|
|
|
641
|
+
## Links
|
|
642
|
+
|
|
643
|
+
- **RubyGems**: [rubygems.org/gems/openvox-lint](https://rubygems.org/gems/openvox-lint)
|
|
644
|
+
- **GitHub**: [github.com/cvquesty/openvox-lint](https://github.com/cvquesty/openvox-lint)
|
|
645
|
+
- **Issues**: [github.com/cvquesty/openvox-lint/issues](https://github.com/cvquesty/openvox-lint/issues)
|
|
646
|
+
|
|
640
647
|
## License
|
|
641
648
|
|
|
642
649
|
Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
|
|
@@ -83,7 +83,8 @@ module OpenvoxLint
|
|
|
83
83
|
return false if @ignore_comments.empty?
|
|
84
84
|
line = problem[:line]
|
|
85
85
|
@ignore_comments.any? do |ic|
|
|
86
|
-
ic[:
|
|
86
|
+
line >= ic[:start_line] && line <= ic[:end_line] &&
|
|
87
|
+
(ic[:checks].empty? || ic[:checks].include?(problem[:check].to_s))
|
|
87
88
|
end
|
|
88
89
|
end
|
|
89
90
|
|
data/lib/openvox-lint/checks.rb
CHANGED
|
@@ -30,17 +30,61 @@ module OpenvoxLint
|
|
|
30
30
|
|
|
31
31
|
private
|
|
32
32
|
|
|
33
|
+
# Parse inline and block-style lint:ignore comments.
|
|
34
|
+
#
|
|
35
|
+
# Inline: # lint:ignore:check_name — suppresses on the same line
|
|
36
|
+
# Block: # lint:ignore:check_name
|
|
37
|
+
# ...code...
|
|
38
|
+
# # lint:endignore — suppresses from ignore to endignore
|
|
33
39
|
def parse_ignore_comments
|
|
34
40
|
results = []
|
|
41
|
+
open_blocks = [] # stack of { start_line:, checks: }
|
|
42
|
+
|
|
35
43
|
@tokens.each do |tok|
|
|
36
44
|
next unless tok.type == :COMMENT
|
|
37
|
-
|
|
38
|
-
|
|
45
|
+
|
|
46
|
+
if tok.value =~ /lint:endignore/
|
|
47
|
+
# Close the most recent open block
|
|
48
|
+
block = open_blocks.pop
|
|
49
|
+
if block
|
|
50
|
+
block[:end_line] = tok.line
|
|
51
|
+
results << block
|
|
52
|
+
end
|
|
53
|
+
elsif tok.value =~ /lint:ignore:(.+)/
|
|
54
|
+
checks = Regexp.last_match(1).strip.split(/\s*,\s*/)
|
|
55
|
+
# If there's code on the same line before this comment, treat
|
|
56
|
+
# it as inline-only (same line). Otherwise open a block.
|
|
57
|
+
if inline_ignore?(tok)
|
|
58
|
+
results << { start_line: tok.line, end_line: tok.line, checks: checks }
|
|
59
|
+
else
|
|
60
|
+
open_blocks.push({ start_line: tok.line, end_line: nil, checks: checks })
|
|
61
|
+
end
|
|
39
62
|
elsif tok.value =~ /lint:ignore\b/
|
|
40
|
-
|
|
63
|
+
if inline_ignore?(tok)
|
|
64
|
+
results << { start_line: tok.line, end_line: tok.line, checks: [] }
|
|
65
|
+
else
|
|
66
|
+
open_blocks.push({ start_line: tok.line, end_line: nil, checks: [] })
|
|
67
|
+
end
|
|
41
68
|
end
|
|
42
69
|
end
|
|
70
|
+
|
|
71
|
+
# Any unclosed blocks extend to end-of-file
|
|
72
|
+
open_blocks.each do |block|
|
|
73
|
+
block[:end_line] = Float::INFINITY
|
|
74
|
+
results << block
|
|
75
|
+
end
|
|
76
|
+
|
|
43
77
|
results
|
|
44
78
|
end
|
|
79
|
+
|
|
80
|
+
# An ignore comment is "inline" if there is a non-formatting token
|
|
81
|
+
# on the same line before it (i.e. it sits at the end of a code line).
|
|
82
|
+
def inline_ignore?(comment_token)
|
|
83
|
+
@tokens.any? do |t|
|
|
84
|
+
t.line == comment_token.line &&
|
|
85
|
+
t.column < comment_token.column &&
|
|
86
|
+
!t.formatting?
|
|
87
|
+
end
|
|
88
|
+
end
|
|
45
89
|
end
|
|
46
90
|
end
|
data/lib/openvox-lint/cli.rb
CHANGED
|
@@ -6,11 +6,12 @@ module OpenvoxLint
|
|
|
6
6
|
# Command-line interface for openvox-lint.
|
|
7
7
|
class CLI
|
|
8
8
|
def initialize(args = ARGV)
|
|
9
|
-
@args
|
|
10
|
-
@config = OpenvoxLint.configuration
|
|
9
|
+
@args = args
|
|
11
10
|
end
|
|
12
11
|
|
|
13
12
|
def run
|
|
13
|
+
OpenvoxLint.reset_configuration!
|
|
14
|
+
@config = OpenvoxLint.configuration
|
|
14
15
|
parse_options
|
|
15
16
|
load_rc_file
|
|
16
17
|
if @list_checks
|
|
@@ -41,7 +42,7 @@ module OpenvoxLint
|
|
|
41
42
|
opts.on('--only-checks CHECKS', 'Comma-separated checks') { |c| @config.only_checks = c.split(',').map { |s| s.strip.to_sym } }
|
|
42
43
|
opts.on('--ignore-paths PATHS', 'Comma-separated globs') { |p| @config.ignore_paths = p.split(',').map(&:strip) }
|
|
43
44
|
opts.on('--list-checks', 'List available checks') { @list_checks = true }
|
|
44
|
-
opts.on('-c', '--config FILE', 'Config file path') { |f| @
|
|
45
|
+
opts.on('-c', '--config FILE', 'Config file path') { |f| @explicit_config_file = f }
|
|
45
46
|
end
|
|
46
47
|
remaining = []
|
|
47
48
|
begin
|
|
@@ -59,7 +60,15 @@ module OpenvoxLint
|
|
|
59
60
|
end
|
|
60
61
|
|
|
61
62
|
def load_rc_file
|
|
62
|
-
|
|
63
|
+
# Priority: explicit --config flag > local .openvox-lint.rc > ~/.openvox-lint.rc
|
|
64
|
+
# An explicit --config flag must always win. The default value of
|
|
65
|
+
# config_file is nil until the user passes -c / --config.
|
|
66
|
+
candidates = if @explicit_config_file
|
|
67
|
+
[@explicit_config_file]
|
|
68
|
+
else
|
|
69
|
+
['.openvox-lint.rc', File.expand_path('~/.openvox-lint.rc')]
|
|
70
|
+
end
|
|
71
|
+
candidates.each do |path|
|
|
63
72
|
next unless path && File.exist?(path)
|
|
64
73
|
@config.load_from_rc(path); break
|
|
65
74
|
end
|
|
@@ -7,13 +7,12 @@ module OpenvoxLint
|
|
|
7
7
|
log_format: 'text', with_filename: true, fail_on_warnings: false,
|
|
8
8
|
fix: false, only_checks: [], disabled_checks: [],
|
|
9
9
|
ignore_paths: %w[vendor/**/*.pp pkg/**/*.pp spec/**/*.pp],
|
|
10
|
-
|
|
11
|
-
custom_log_format: nil,
|
|
10
|
+
relative: false, column: true, custom_log_format: nil,
|
|
12
11
|
}.freeze
|
|
13
12
|
|
|
14
13
|
attr_accessor :log_format, :with_filename, :fail_on_warnings,
|
|
15
14
|
:fix, :only_checks, :disabled_checks, :ignore_paths,
|
|
16
|
-
:
|
|
15
|
+
:relative, :column, :custom_log_format
|
|
17
16
|
|
|
18
17
|
def initialize
|
|
19
18
|
DEFAULTS.each do |k, v|
|
data/lib/openvox-lint/lexer.rb
CHANGED
|
@@ -140,8 +140,9 @@ module OpenvoxLint
|
|
|
140
140
|
end
|
|
141
141
|
|
|
142
142
|
def scan_regex
|
|
143
|
-
start = @pos; start_col = @column
|
|
143
|
+
start = @pos; start_line = @line; start_col = @column
|
|
144
144
|
@pos += 1; @column += 1
|
|
145
|
+
terminated = false
|
|
145
146
|
while @pos < @code.length && @code[@pos] != '/'
|
|
146
147
|
if @code[@pos] == '\\'
|
|
147
148
|
@pos += 2; @column += 2
|
|
@@ -149,31 +150,41 @@ module OpenvoxLint
|
|
|
149
150
|
@pos += 1; @column += 1
|
|
150
151
|
end
|
|
151
152
|
end
|
|
152
|
-
@pos
|
|
153
|
-
|
|
153
|
+
if @pos < @code.length
|
|
154
|
+
@pos += 1; @column += 1; terminated = true
|
|
155
|
+
end
|
|
156
|
+
if !terminated
|
|
157
|
+
raise OpenvoxLint::Error, "unterminated regex starting at line #{start_line}"
|
|
158
|
+
end
|
|
159
|
+
add_token(:REGEX, @code[start...@pos], start_line, start_col)
|
|
154
160
|
end
|
|
155
161
|
|
|
156
162
|
def scan_single_quoted_string
|
|
157
|
-
start = @pos; start_col = @column
|
|
163
|
+
start = @pos; start_line = @line; start_col = @column
|
|
158
164
|
@pos += 1; @column += 1
|
|
165
|
+
terminated = false
|
|
159
166
|
while @pos < @code.length
|
|
160
167
|
if @code[@pos] == '\\'
|
|
161
168
|
@pos += 2; @column += 2
|
|
162
169
|
elsif @code[@pos] == "'"
|
|
163
|
-
@pos += 1; @column += 1; break
|
|
170
|
+
@pos += 1; @column += 1; terminated = true; break
|
|
164
171
|
elsif @code[@pos] == "\n"
|
|
165
172
|
@line += 1; @column = 1; @pos += 1
|
|
166
173
|
else
|
|
167
174
|
@pos += 1; @column += 1
|
|
168
175
|
end
|
|
169
176
|
end
|
|
170
|
-
|
|
177
|
+
if !terminated
|
|
178
|
+
raise OpenvoxLint::Error, "unterminated single-quoted string starting at line #{start_line}"
|
|
179
|
+
end
|
|
180
|
+
add_token(:SSTRING, @code[start...@pos], start_line, start_col)
|
|
171
181
|
end
|
|
172
182
|
|
|
173
183
|
def scan_double_quoted_string
|
|
174
|
-
start = @pos; start_col = @column
|
|
184
|
+
start = @pos; start_line = @line; start_col = @column
|
|
175
185
|
@pos += 1; @column += 1
|
|
176
186
|
has_interp = false
|
|
187
|
+
terminated = false
|
|
177
188
|
while @pos < @code.length
|
|
178
189
|
if @code[@pos] == '\\'
|
|
179
190
|
@pos += 2; @column += 2
|
|
@@ -184,15 +195,18 @@ module OpenvoxLint
|
|
|
184
195
|
@pos += 1; @column += 1
|
|
185
196
|
end
|
|
186
197
|
elsif @code[@pos] == '"'
|
|
187
|
-
@pos += 1; @column += 1; break
|
|
198
|
+
@pos += 1; @column += 1; terminated = true; break
|
|
188
199
|
elsif @code[@pos] == "\n"
|
|
189
200
|
@line += 1; @column = 1; @pos += 1
|
|
190
201
|
else
|
|
191
202
|
@pos += 1; @column += 1
|
|
192
203
|
end
|
|
193
204
|
end
|
|
205
|
+
if !terminated
|
|
206
|
+
raise OpenvoxLint::Error, "unterminated double-quoted string starting at line #{start_line}"
|
|
207
|
+
end
|
|
194
208
|
type = has_interp ? :DQSTRING : :STRING
|
|
195
|
-
add_token(type, @code[start...@pos],
|
|
209
|
+
add_token(type, @code[start...@pos], start_line, start_col)
|
|
196
210
|
end
|
|
197
211
|
|
|
198
212
|
def scan_variable
|
data/lib/openvox-lint/linter.rb
CHANGED
|
@@ -64,8 +64,14 @@ module OpenvoxLint
|
|
|
64
64
|
end
|
|
65
65
|
|
|
66
66
|
def ignored?(filepath)
|
|
67
|
+
# Normalize away leading './' so that patterns like 'vendor/**/*.pp'
|
|
68
|
+
# match regardless of whether the file was discovered as
|
|
69
|
+
# './vendor/foo.pp' or 'vendor/foo.pp'.
|
|
70
|
+
normalized = filepath.sub(%r{\A\./}, '')
|
|
67
71
|
@config.ignore_paths.any? do |pat|
|
|
68
|
-
File.fnmatch?(pat,
|
|
72
|
+
File.fnmatch?(pat, normalized, File::FNM_PATHNAME | File::FNM_DOTMATCH) ||
|
|
73
|
+
File.fnmatch?(pat, filepath, File::FNM_PATHNAME | File::FNM_DOTMATCH) ||
|
|
74
|
+
File.fnmatch?("**/#{pat}", normalized, File::FNM_PATHNAME | File::FNM_DOTMATCH)
|
|
69
75
|
end
|
|
70
76
|
end
|
|
71
77
|
end
|
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Hash rockets (=>) should be aligned within a resource body.
|
|
4
|
+
#
|
|
5
|
+
# Only fires on groups of 2+ arrows. Skips groups where the
|
|
6
|
+
# misalignment is caused by the longest key having extra leading
|
|
7
|
+
# whitespace — that case is already reported by the
|
|
8
|
+
# space_before_arrow check to avoid duplicate / contradictory advice.
|
|
4
9
|
OpenvoxLint.new_check(:arrow_alignment) do
|
|
5
10
|
def check
|
|
6
11
|
# Group FARROW tokens by resource (consecutive lines)
|
|
@@ -11,8 +16,16 @@ OpenvoxLint.new_check(:arrow_alignment) do
|
|
|
11
16
|
groups = group_arrows(arrows)
|
|
12
17
|
groups.each do |group|
|
|
13
18
|
next if group.size <= 1
|
|
19
|
+
|
|
20
|
+
# If the arrows are already at the same column, nothing to do.
|
|
14
21
|
columns = group.map(&:column)
|
|
15
22
|
max_col = columns.max
|
|
23
|
+
next if columns.all? { |c| c == max_col }
|
|
24
|
+
|
|
25
|
+
# Skip this group if the misalignment is caused by extra space
|
|
26
|
+
# before the longest-key arrow — space_before_arrow handles that.
|
|
27
|
+
next if longest_key_has_extra_space?(group)
|
|
28
|
+
|
|
16
29
|
group.each do |arrow|
|
|
17
30
|
next if arrow.column == max_col
|
|
18
31
|
notify :warning,
|
|
@@ -39,4 +52,29 @@ OpenvoxLint.new_check(:arrow_alignment) do
|
|
|
39
52
|
groups << current unless current.empty?
|
|
40
53
|
groups
|
|
41
54
|
end
|
|
55
|
+
|
|
56
|
+
# Returns true when the arrow belonging to the longest key in the
|
|
57
|
+
# group has more than one space before it. In that situation the
|
|
58
|
+
# space_before_arrow check will fire, so arrow_alignment should stay
|
|
59
|
+
# silent to avoid contradictory advice.
|
|
60
|
+
def longest_key_has_extra_space?(group)
|
|
61
|
+
group.each do |arrow|
|
|
62
|
+
idx = tokens.index(arrow)
|
|
63
|
+
next unless idx && idx >= 2
|
|
64
|
+
ws = tokens[idx - 1]
|
|
65
|
+
next unless ws.type == :WHITESPACE
|
|
66
|
+
key = tokens[idx - 2]
|
|
67
|
+
next if key.formatting?
|
|
68
|
+
# Find the longest key length in the group
|
|
69
|
+
max_klen = group.map { |a|
|
|
70
|
+
ai = tokens.index(a)
|
|
71
|
+
next 0 unless ai && ai >= 2 && tokens[ai - 1].type == :WHITESPACE
|
|
72
|
+
tokens[ai - 2].value.length
|
|
73
|
+
}.max
|
|
74
|
+
if key.value.length == max_klen && ws.value.length > 1
|
|
75
|
+
return true
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
false
|
|
79
|
+
end
|
|
42
80
|
end
|
|
@@ -8,9 +8,9 @@ OpenvoxLint.new_check(:duplicate_params) do
|
|
|
8
8
|
params = res[:param_tokens]
|
|
9
9
|
params.each_with_index do |tok, i|
|
|
10
10
|
next unless tok.type == :NAME
|
|
11
|
-
#
|
|
11
|
+
# param_tokens are already semantic (non-formatting), so the
|
|
12
|
+
# next token is directly adjacent.
|
|
12
13
|
j = i + 1
|
|
13
|
-
j += 1 while j < params.length && params[j].formatting?
|
|
14
14
|
next unless j < params.length && params[j].type == :FARROW
|
|
15
15
|
if seen[tok.value]
|
|
16
16
|
notify :error,
|
|
@@ -37,7 +37,9 @@ OpenvoxLint.new_check(:legacy_facts) do
|
|
|
37
37
|
def check
|
|
38
38
|
tokens.each do |tok|
|
|
39
39
|
next unless tok.type == :VARIABLE
|
|
40
|
-
|
|
40
|
+
# Strip leading $ and :: prefix, and any trailing : left by the
|
|
41
|
+
# lexer when a variable is used as a resource title ($fact:).
|
|
42
|
+
name = tok.value.sub(/^\$/, '').sub(/\A::/, '').chomp(':')
|
|
41
43
|
next unless LEGACY_FACTS.include?(name)
|
|
42
44
|
notify :warning,
|
|
43
45
|
message: "legacy fact '#{name}' — use $facts['...'] structured fact instead (Puppet 8 / OpenVox 8)",
|
|
@@ -8,9 +8,9 @@ OpenvoxLint.new_check(:parameter_order) do
|
|
|
8
8
|
found_default = false
|
|
9
9
|
params.each_with_index do |tok, i|
|
|
10
10
|
next unless tok.type == :VARIABLE
|
|
11
|
-
#
|
|
11
|
+
# param_tokens from find_keyword_indexes are already semantic
|
|
12
|
+
# (non-formatting), so the next token is directly adjacent.
|
|
12
13
|
j = i + 1
|
|
13
|
-
j += 1 while j < params.length && params[j].formatting?
|
|
14
14
|
has_default = j < params.length && params[j].type == :EQUALS
|
|
15
15
|
if has_default
|
|
16
16
|
found_default = true
|
|
@@ -3,6 +3,21 @@
|
|
|
3
3
|
# Resource reference titles must start with a capital letter.
|
|
4
4
|
# e.g. File['/tmp/foo'] not file['/tmp/foo']
|
|
5
5
|
OpenvoxLint.new_check(:resource_reference_without_title_capital) do
|
|
6
|
+
# Puppet built-in functions and common stdlib functions that accept
|
|
7
|
+
# bracket/index syntax (name[...]). These are NOT resource
|
|
8
|
+
# references and must not be flagged.
|
|
9
|
+
FUNCTION_ALLOWLIST = %w[
|
|
10
|
+
split join include contain require
|
|
11
|
+
each map filter reduce select reject
|
|
12
|
+
slice flatten any all empty
|
|
13
|
+
assert_type create_resources defined dig
|
|
14
|
+
ensure_packages ensure_resource epp fail
|
|
15
|
+
fqdn_rand generate hiera hiera_array hiera_hash hiera_include
|
|
16
|
+
inline_epp inline_template lookup match md5 notice
|
|
17
|
+
realize regsubst sha1 sprintf tag template unique versioncmp
|
|
18
|
+
with
|
|
19
|
+
].freeze
|
|
20
|
+
|
|
6
21
|
def check
|
|
7
22
|
sem = semantic_tokens
|
|
8
23
|
sem.each_with_index do |tok, i|
|
|
@@ -11,8 +26,8 @@ OpenvoxLint.new_check(:resource_reference_without_title_capital) do
|
|
|
11
26
|
next unless sem[i + 1].type == :LBRACK
|
|
12
27
|
# This is name[...] which should be Name[...] for a resource reference
|
|
13
28
|
next if tok.value =~ /\A[A-Z]/
|
|
14
|
-
# Skip function calls and normal array access
|
|
15
|
-
next if
|
|
29
|
+
# Skip function calls, method calls, and normal array access
|
|
30
|
+
next if FUNCTION_ALLOWLIST.include?(tok.value)
|
|
16
31
|
notify :warning,
|
|
17
32
|
message: "resource reference '#{tok.value}' must start with a capital letter",
|
|
18
33
|
line: tok.line, column: tok.column
|
|
@@ -6,7 +6,9 @@ OpenvoxLint.new_check(:top_scope_facts) do
|
|
|
6
6
|
def check
|
|
7
7
|
tokens.each do |tok|
|
|
8
8
|
next unless tok.type == :VARIABLE
|
|
9
|
-
|
|
9
|
+
# Strip leading $ and any trailing : left by the lexer when a
|
|
10
|
+
# variable is used as a resource title ($::fact:).
|
|
11
|
+
name = tok.value.sub(/^\$/, '').chomp(':')
|
|
10
12
|
next unless name.start_with?('::')
|
|
11
13
|
fact_name = name.sub(/\A::/, '')
|
|
12
14
|
# Skip module-qualified variables (e.g. ::mymodule::param)
|
|
@@ -1,24 +1,30 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
# Resource bodies and parameter lists should end with a trailing comma.
|
|
4
|
+
#
|
|
5
|
+
# Only fires inside resource bodies (NAME { ... }) — not inside
|
|
6
|
+
# conditionals (if/unless/case), class bodies, or other brace contexts
|
|
7
|
+
# where a trailing comma is not expected.
|
|
4
8
|
OpenvoxLint.new_check(:trailing_comma) do
|
|
5
9
|
def check
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
10
|
+
resource_indexes.each do |res|
|
|
11
|
+
params = res[:param_tokens]
|
|
12
|
+
next if params.empty?
|
|
13
|
+
|
|
14
|
+
# Find the last non-formatting token in the resource body
|
|
15
|
+
last = params.reverse_each.find { |t| !t.formatting? }
|
|
16
|
+
next unless last
|
|
17
|
+
|
|
18
|
+
# Already has a trailing comma — nothing to do
|
|
19
|
+
next if last.type == :COMMA
|
|
20
|
+
|
|
21
|
+
# Only flag after value-like tokens (not structural tokens)
|
|
22
|
+
next unless %i[SSTRING STRING NAME VARIABLE NUMBER TRUE FALSE
|
|
23
|
+
CLASSREF RBRACK DQSTRING].include?(last.type)
|
|
24
|
+
|
|
19
25
|
notify :warning,
|
|
20
26
|
message: 'missing trailing comma after last attribute',
|
|
21
|
-
line:
|
|
27
|
+
line: last.line, column: last.column
|
|
22
28
|
end
|
|
23
29
|
end
|
|
24
30
|
end
|
|
@@ -2,18 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
# Variables in double-quoted strings should be enclosed in braces.
|
|
4
4
|
# e.g. "$foo" should be "${foo}"
|
|
5
|
+
#
|
|
6
|
+
# Correctly handles strings with a mix of enclosed and unenclosed
|
|
7
|
+
# variables — e.g. "$foo and ${bar}" flags the unenclosed $foo even
|
|
8
|
+
# though ${bar} is properly enclosed.
|
|
5
9
|
OpenvoxLint.new_check(:variables_not_enclosed) do
|
|
6
10
|
def check
|
|
7
11
|
tokens.each do |tok|
|
|
8
12
|
next unless tok.type == :DQSTRING || tok.type == :STRING
|
|
9
13
|
val = tok.value
|
|
10
|
-
#
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
14
|
+
# Scan for any $var that is NOT preceded by ${ (already enclosed).
|
|
15
|
+
# We use a negative lookbehind to skip ${...} patterns and only
|
|
16
|
+
# match bare $varname references.
|
|
17
|
+
val.scan(/(?<!\$)\$(?!\{)([a-zA-Z_][a-zA-Z0-9_:]*)/) do
|
|
18
|
+
notify :warning,
|
|
19
|
+
message: 'variable not enclosed in braces within string',
|
|
20
|
+
line: tok.line,
|
|
21
|
+
column: tok.column
|
|
22
|
+
end
|
|
17
23
|
end
|
|
18
24
|
end
|
|
19
25
|
end
|
data/lib/openvox-lint/version.rb
CHANGED
data/lib/openvox-lint.rb
CHANGED
|
@@ -29,11 +29,21 @@ module OpenvoxLint
|
|
|
29
29
|
yield(configuration)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
+
# Reset the global configuration singleton. Called at the start of
|
|
33
|
+
# every CLI run so that repeated invocations in the same Ruby
|
|
34
|
+
# process (Vim plugins, guard, Rake loops) start clean.
|
|
35
|
+
def reset_configuration!
|
|
36
|
+
@configuration = Configuration.new
|
|
37
|
+
end
|
|
38
|
+
|
|
32
39
|
def checks
|
|
33
40
|
@checks ||= {}
|
|
34
41
|
end
|
|
35
42
|
|
|
36
43
|
def new_check(name, &block)
|
|
44
|
+
if checks.key?(name)
|
|
45
|
+
$stderr.puts "openvox-lint: warning: check '#{name}' is already registered — overwriting" if ENV['OPENVOX_LINT_DEBUG']
|
|
46
|
+
end
|
|
37
47
|
klass = Class.new(CheckPlugin, &block)
|
|
38
48
|
klass.instance_variable_set(:@check_name, name)
|
|
39
49
|
checks[name] = klass
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: openvox-lint
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.0.
|
|
4
|
+
version: 1.0.8
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Jerald Sheets
|
|
8
|
-
autorequire:
|
|
8
|
+
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-
|
|
11
|
+
date: 2026-03-04 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -126,7 +126,7 @@ metadata:
|
|
|
126
126
|
bug_tracker_uri: https://github.com/cvquesty/openvox-lint/issues
|
|
127
127
|
changelog_uri: https://github.com/cvquesty/openvox-lint/blob/main/CHANGELOG.md
|
|
128
128
|
rubygems_mfa_required: 'true'
|
|
129
|
-
post_install_message:
|
|
129
|
+
post_install_message:
|
|
130
130
|
rdoc_options: []
|
|
131
131
|
require_paths:
|
|
132
132
|
- lib
|
|
@@ -141,8 +141,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
141
141
|
- !ruby/object:Gem::Version
|
|
142
142
|
version: '0'
|
|
143
143
|
requirements: []
|
|
144
|
-
rubygems_version: 3.
|
|
145
|
-
signing_key:
|
|
144
|
+
rubygems_version: 3.4.19
|
|
145
|
+
signing_key:
|
|
146
146
|
specification_version: 4
|
|
147
147
|
summary: Check OpenVox/Puppet manifests against the style guide
|
|
148
148
|
test_files: []
|