openvox-lint 1.0.7 → 1.3.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/CHANGELOG.md +178 -1
- data/DOCUMENTATION.md +1392 -259
- data/README.md +53 -15
- 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 +25 -1
- data/lib/openvox-lint/plugins/checks/arrow_alignment.rb +38 -0
- data/lib/openvox-lint/plugins/checks/double_quoted_strings.rb +16 -0
- data/lib/openvox-lint/plugins/checks/duplicate_params.rb +2 -2
- data/lib/openvox-lint/plugins/checks/hard_tabs.rb +7 -0
- data/lib/openvox-lint/plugins/checks/hiera3_function.rb +13 -3
- data/lib/openvox-lint/plugins/checks/legacy_facts.rb +47 -1
- data/lib/openvox-lint/plugins/checks/parameter_order.rb +2 -2
- data/lib/openvox-lint/plugins/checks/quoted_booleans.rb +21 -0
- data/lib/openvox-lint/plugins/checks/resource_reference_without_title_capital.rb +17 -2
- data/lib/openvox-lint/plugins/checks/single_quote_string_with_variables.rb +16 -0
- 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/trailing_whitespace.rb +6 -0
- 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 +16 -12
- data/lib/openvox-lint/plugins/checks/relative_classname_inclusion.rb +0 -24
data/README.md
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
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
8
|

|
|
8
9
|
|
|
9
10
|
**A style-guide linter for OpenVox and Puppet manifests.**
|
|
10
11
|
|
|
11
|
-
openvox-lint checks your `.pp` manifest files against the [
|
|
12
|
+
openvox-lint checks your `.pp` manifest files against the [OpenVox Language Style Guide](https://docs.openvoxproject.org/openvox/latest/style_guide.html) (and Puppet equivalent) and catches common errors, deprecated patterns, legacy facts, strict-mode violations, and Puppet 8+ / OpenVox 8.x language issues.
|
|
12
13
|
|
|
13
14
|
Fully compatible with:
|
|
14
15
|
- **OpenVox 8.x** (the community-maintained open-source fork of Puppet)
|
|
@@ -24,7 +25,7 @@ Fully compatible with:
|
|
|
24
25
|
- [Quick Start](#quick-start)
|
|
25
26
|
- [Usage](#usage)
|
|
26
27
|
- [Enabling and Disabling Checks](#enabling-and-disabling-checks)
|
|
27
|
-
- [Built-in Checks (
|
|
28
|
+
- [Built-in Checks (37)](#built-in-checks)
|
|
28
29
|
- [Configuration](#configuration)
|
|
29
30
|
- [Output Formats](#output-formats)
|
|
30
31
|
- [Integration](#integration)
|
|
@@ -32,6 +33,7 @@ Fully compatible with:
|
|
|
32
33
|
- [Updating](#updating)
|
|
33
34
|
- [Uninstalling](#uninstalling)
|
|
34
35
|
- [Development](#development)
|
|
36
|
+
- [Contributors](#contributors)
|
|
35
37
|
- [License](#license)
|
|
36
38
|
|
|
37
39
|
---
|
|
@@ -158,6 +160,27 @@ openvox-lint -f github manifests/
|
|
|
158
160
|
openvox-lint --fail-on-warnings manifests/
|
|
159
161
|
```
|
|
160
162
|
|
|
163
|
+
### Fix mode (`--fix`)
|
|
164
|
+
|
|
165
|
+
`--fix` automatically corrects problems for the checks that implement fix logic. Fixes are applied in-place and are safe for the supported cases.
|
|
166
|
+
|
|
167
|
+
Currently supported checks:
|
|
168
|
+
- `trailing_whitespace` — removes trailing spaces/tabs
|
|
169
|
+
- `hard_tabs` — converts tabs to two spaces
|
|
170
|
+
- `quoted_booleans` — changes `'true'` / `'false'` (strings) to unquoted booleans
|
|
171
|
+
- `double_quoted_strings` — converts unnecessary double-quoted strings (no interpolation or escapes) to single quotes
|
|
172
|
+
- `single_quote_string_with_variables` — converts single-quoted strings containing `$variables` to double quotes so interpolation works
|
|
173
|
+
|
|
174
|
+
Example:
|
|
175
|
+
|
|
176
|
+
```bash
|
|
177
|
+
openvox-lint --fix manifests/
|
|
178
|
+
# or
|
|
179
|
+
openvox-lint --fix .
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
After running, re-lint to confirm. Complex structural fixes (e.g. arrow alignment) are not yet implemented.
|
|
183
|
+
|
|
161
184
|
---
|
|
162
185
|
|
|
163
186
|
## Enabling and Disabling Checks
|
|
@@ -181,7 +204,7 @@ openvox-lint --no-line_length-check --no-arrow_alignment-check --no-strict_inden
|
|
|
181
204
|
Use `--only-checks` to run *only* the checks you specify (everything else is skipped):
|
|
182
205
|
|
|
183
206
|
```bash
|
|
184
|
-
# Only check for legacy facts and
|
|
207
|
+
# Only check for legacy facts and deprecated Hiera 3 functions
|
|
185
208
|
openvox-lint --only-checks legacy_facts,hiera3_function,top_scope_facts manifests/
|
|
186
209
|
|
|
187
210
|
# Only check for errors (skip all warnings)
|
|
@@ -231,13 +254,13 @@ To see every check that openvox-lint can run:
|
|
|
231
254
|
openvox-lint --list-checks
|
|
232
255
|
```
|
|
233
256
|
|
|
234
|
-
This shows the check name, severity (warning or error), and description for all
|
|
257
|
+
This shows the check name, severity (warning or error), and description for all 37 built-in checks.
|
|
235
258
|
|
|
236
259
|
---
|
|
237
260
|
|
|
238
261
|
## Built-in Checks
|
|
239
262
|
|
|
240
|
-
openvox-lint ships with **
|
|
263
|
+
openvox-lint ships with **37 built-in checks** organized into categories:
|
|
241
264
|
|
|
242
265
|
### Whitespace & Formatting (5 checks)
|
|
243
266
|
|
|
@@ -301,13 +324,12 @@ openvox-lint ships with **38 built-in checks** organized into categories:
|
|
|
301
324
|
| `case_without_default` | warning | Case statements must have a `default` case |
|
|
302
325
|
| `selector_inside_resource` | warning | Don't use selectors inside resource bodies |
|
|
303
326
|
|
|
304
|
-
### References & Syntax (
|
|
327
|
+
### References & Syntax (3 checks)
|
|
305
328
|
|
|
306
329
|
| Check | Severity | Description |
|
|
307
330
|
|-------|----------|-------------|
|
|
308
331
|
| `leading_zero` | warning | No leading zeros in numbers (except file modes) |
|
|
309
332
|
| `resource_reference_without_title_capital` | warning | Capitalise resource reference types |
|
|
310
|
-
| `relative_classname_inclusion` | warning | Use fully qualified class names |
|
|
311
333
|
| `autoloader_layout` | warning | Class/define name must match file path |
|
|
312
334
|
|
|
313
335
|
### Comments (1 check)
|
|
@@ -334,7 +356,7 @@ openvox-lint ships with **38 built-in checks** organized into categories:
|
|
|
334
356
|
|-------|----------|-------------|
|
|
335
357
|
| `legacy_facts` | warning | Legacy facts removed in Puppet 8; use `$facts['...']` |
|
|
336
358
|
| `top_scope_facts` | warning | `$::fact` references; use `$facts['fact']` |
|
|
337
|
-
| `hiera3_function` | **error** |
|
|
359
|
+
| `hiera3_function` | **error** | Deprecated Hiera 3 functions removed; use Hiera 5 `lookup()` |
|
|
338
360
|
| `import_statement` | **error** | `import` removed in Puppet 4+ |
|
|
339
361
|
|
|
340
362
|
---
|
|
@@ -381,7 +403,7 @@ class foo { # lint:ignore:documentation
|
|
|
381
403
|
|
|
382
404
|
```
|
|
383
405
|
manifests/init.pp:5:15: WARNING: unquoted_file_mode: unquoted file mode
|
|
384
|
-
manifests/init.pp:9:3: ERROR: hiera3_function: 'hiera()'
|
|
406
|
+
manifests/init.pp:9:3: ERROR: hiera3_function: deprecated Hiera 3 function 'hiera()' — use Hiera 5 lookup() instead
|
|
385
407
|
```
|
|
386
408
|
|
|
387
409
|
### JSON (`-f json`)
|
|
@@ -616,14 +638,14 @@ openvox-lint/
|
|
|
616
638
|
|
|
617
639
|
## Comparison with puppet-lint
|
|
618
640
|
|
|
619
|
-
| Feature | puppet-lint 5.x | openvox-lint 1.0 |
|
|
641
|
+
| Feature | puppet-lint 5.x | openvox-lint 1.3.0 |
|
|
620
642
|
|---------|-----------------|------------------|
|
|
621
643
|
| Ruby requirement | ≥ 3.1 | ≥ 2.5 (RHEL 8, macOS, all modern platforms) |
|
|
622
644
|
| Runtime dependencies | None | None |
|
|
623
|
-
| Built-in checks | ~25 |
|
|
645
|
+
| Built-in checks | ~25 | 37 |
|
|
624
646
|
| Legacy facts detection | Via plugin | Built-in |
|
|
625
647
|
| Top-scope facts detection | Via plugin | Built-in |
|
|
626
|
-
| Hiera 3 detection | No | Built-in (error) |
|
|
648
|
+
| Deprecated Hiera 3 function detection | No | Built-in (error) |
|
|
627
649
|
| Import statement detection | No | Built-in (error) |
|
|
628
650
|
| Strict indent check | Via plugin | Built-in |
|
|
629
651
|
| GitHub Actions output | No | Built-in (`-f github`) |
|
|
@@ -632,17 +654,33 @@ openvox-lint/
|
|
|
632
654
|
| Custom log format | Yes | Yes (compatible) |
|
|
633
655
|
| OpenVox awareness | No | Yes |
|
|
634
656
|
| Plugin system | Yes | Yes (compatible) |
|
|
635
|
-
| `--fix` support | Yes | Yes |
|
|
657
|
+
| `--fix` support | Yes | Yes (real implementations for trailing_whitespace, hard_tabs, quoted_booleans, double_quoted_strings, single_quote_string_with_variables + line-based) |
|
|
636
658
|
| vim-openvox integration | No | Native |
|
|
637
659
|
|
|
638
660
|
---
|
|
639
661
|
|
|
662
|
+
## Links
|
|
663
|
+
|
|
664
|
+
- **RubyGems**: [rubygems.org/gems/openvox-lint](https://rubygems.org/gems/openvox-lint)
|
|
665
|
+
- **GitHub**: [github.com/cvquesty/openvox-lint](https://github.com/cvquesty/openvox-lint)
|
|
666
|
+
- **Issues**: [github.com/cvquesty/openvox-lint/issues](https://github.com/cvquesty/openvox-lint/issues)
|
|
667
|
+
|
|
640
668
|
## License
|
|
641
669
|
|
|
642
670
|
Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
|
|
643
671
|
|
|
644
672
|
---
|
|
645
673
|
|
|
674
|
+
## Contributors
|
|
675
|
+
|
|
676
|
+
Thanks to the following people for their contributions to openvox-lint:
|
|
677
|
+
|
|
678
|
+
| Contributor | Contribution |
|
|
679
|
+
|-------------|--------------|
|
|
680
|
+
| **[@hbro](https://github.com/hbro)** (Hans Broeckx) | Fixed `legacy_facts` false positives on local variables ([PR #1](https://github.com/cvquesty/openvox-lint/pull/1)) — *First community contribution!* 🎉 |
|
|
681
|
+
|
|
682
|
+
Want to contribute? See [CONTRIBUTING.md](CONTRIBUTING.md) to get started!
|
|
683
|
+
|
|
646
684
|
## Author
|
|
647
685
|
|
|
648
686
|
Jerald Sheets ([@cvquesty](https://github.com/cvquesty))
|
|
@@ -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
|
@@ -42,6 +42,24 @@ module OpenvoxLint
|
|
|
42
42
|
fullpath: filepath, configuration: @config,
|
|
43
43
|
)
|
|
44
44
|
@problems.concat(checker.run)
|
|
45
|
+
|
|
46
|
+
# Apply --fix: checks' #fix methods (if implemented) mutate the shared
|
|
47
|
+
# manifest_lines array in place. After all checks, write the (possibly
|
|
48
|
+
# fixed) content back if it differs. This makes --fix functional for
|
|
49
|
+
# supported checks. Ensures final newline per style guide.
|
|
50
|
+
if @config.fix
|
|
51
|
+
begin
|
|
52
|
+
fixed_code = lexer.manifest_lines.join("\n") + "\n"
|
|
53
|
+
if fixed_code != code
|
|
54
|
+
File.write(filepath, fixed_code)
|
|
55
|
+
end
|
|
56
|
+
rescue StandardError => write_err
|
|
57
|
+
@problems << {
|
|
58
|
+
path: filepath, line: 0, column: 0, kind: :error,
|
|
59
|
+
check: :fix, message: "Failed to write fixes: #{write_err.message}",
|
|
60
|
+
}
|
|
61
|
+
end
|
|
62
|
+
end
|
|
45
63
|
rescue StandardError => e
|
|
46
64
|
@problems << {
|
|
47
65
|
path: filepath, line: 0, column: 0, kind: :error,
|
|
@@ -64,8 +82,14 @@ module OpenvoxLint
|
|
|
64
82
|
end
|
|
65
83
|
|
|
66
84
|
def ignored?(filepath)
|
|
85
|
+
# Normalize away leading './' so that patterns like 'vendor/**/*.pp'
|
|
86
|
+
# match regardless of whether the file was discovered as
|
|
87
|
+
# './vendor/foo.pp' or 'vendor/foo.pp'.
|
|
88
|
+
normalized = filepath.sub(%r{\A\./}, '')
|
|
67
89
|
@config.ignore_paths.any? do |pat|
|
|
68
|
-
File.fnmatch?(pat,
|
|
90
|
+
File.fnmatch?(pat, normalized, File::FNM_PATHNAME | File::FNM_DOTMATCH) ||
|
|
91
|
+
File.fnmatch?(pat, filepath, File::FNM_PATHNAME | File::FNM_DOTMATCH) ||
|
|
92
|
+
File.fnmatch?("**/#{pat}", normalized, File::FNM_PATHNAME | File::FNM_DOTMATCH)
|
|
69
93
|
end
|
|
70
94
|
end
|
|
71
95
|
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
|
|
@@ -24,4 +24,20 @@ OpenvoxLint.new_check(:double_quoted_strings) do
|
|
|
24
24
|
column: tok.column
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
|
+
|
|
28
|
+
# --fix support: convert the double-quoted string (no interp) to single
|
|
29
|
+
# quotes by swapping the outer delimiters at the reported column.
|
|
30
|
+
def fix(problem)
|
|
31
|
+
idx = problem[:line] - 1
|
|
32
|
+
line = @manifest_lines[idx].dup
|
|
33
|
+
col = (problem[:column] || 1) - 1
|
|
34
|
+
if line[col] == '"'
|
|
35
|
+
end_col = line.index('"', col + 1)
|
|
36
|
+
if end_col
|
|
37
|
+
inner = line[(col + 1)...end_col]
|
|
38
|
+
line[col..end_col] = "'" + inner + "'"
|
|
39
|
+
@manifest_lines[idx] = line
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
27
43
|
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,
|
|
@@ -12,4 +12,11 @@ OpenvoxLint.new_check(:hard_tabs) do
|
|
|
12
12
|
column: col + 1
|
|
13
13
|
end
|
|
14
14
|
end
|
|
15
|
+
|
|
16
|
+
# --fix support: replace all hard tabs with two spaces (simple but effective
|
|
17
|
+
# for consistent soft-tab style; more sophisticated indent fixers exist).
|
|
18
|
+
def fix(problem)
|
|
19
|
+
idx = problem[:line] - 1
|
|
20
|
+
@manifest_lines[idx] = @manifest_lines[idx].gsub("\t", ' ')
|
|
21
|
+
end
|
|
15
22
|
end
|
|
@@ -1,7 +1,17 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
# Hiera 3 functions
|
|
4
|
-
#
|
|
3
|
+
# Detects deprecated Hiera 3 functions that are removed in Puppet 8 / OpenVox 8.
|
|
4
|
+
#
|
|
5
|
+
# Hiera 3 is fully deprecated. Only Hiera 5 is supported in Puppet 8+.
|
|
6
|
+
# The legacy hiera() functions were compatibility shims that have been removed.
|
|
7
|
+
#
|
|
8
|
+
# Deprecated functions:
|
|
9
|
+
# - hiera() -> lookup()
|
|
10
|
+
# - hiera_array() -> lookup(..., Array, 'unique')
|
|
11
|
+
# - hiera_hash() -> lookup(..., Hash, 'hash')
|
|
12
|
+
# - hiera_include() -> lookup(...).include
|
|
13
|
+
#
|
|
14
|
+
# Use the Hiera 5 lookup() function instead.
|
|
5
15
|
OpenvoxLint.new_check(:hiera3_function) do
|
|
6
16
|
HIERA3_FUNCS = %w[hiera hiera_array hiera_hash hiera_include].freeze
|
|
7
17
|
|
|
@@ -9,7 +19,7 @@ OpenvoxLint.new_check(:hiera3_function) do
|
|
|
9
19
|
tokens.each do |tok|
|
|
10
20
|
next unless tok.type == :NAME && HIERA3_FUNCS.include?(tok.value)
|
|
11
21
|
notify :error,
|
|
12
|
-
message: "'#{tok.value}()'
|
|
22
|
+
message: "deprecated Hiera 3 function '#{tok.value}()' — use Hiera 5 lookup() instead",
|
|
13
23
|
line: tok.line, column: tok.column
|
|
14
24
|
end
|
|
15
25
|
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'set'
|
|
4
|
+
|
|
3
5
|
# Legacy (unstructured) top-scope facts are removed in Puppet 8 / OpenVox 8.
|
|
4
6
|
# Use structured facts via $facts hash instead.
|
|
5
7
|
#
|
|
@@ -35,13 +37,57 @@ OpenvoxLint.new_check(:legacy_facts) do
|
|
|
35
37
|
].freeze
|
|
36
38
|
|
|
37
39
|
def check
|
|
40
|
+
local_vars = collect_local_vars
|
|
38
41
|
tokens.each do |tok|
|
|
39
42
|
next unless tok.type == :VARIABLE
|
|
40
|
-
|
|
43
|
+
# Strip leading $ and :: prefix, and any trailing : left by the
|
|
44
|
+
# lexer when a variable is used as a resource title ($fact:).
|
|
45
|
+
name = tok.value.sub(/^\$/, '').sub(/\A::/, '').chomp(':')
|
|
41
46
|
next unless LEGACY_FACTS.include?(name)
|
|
47
|
+
next if local_vars.include?(name)
|
|
42
48
|
notify :warning,
|
|
43
49
|
message: "legacy fact '#{name}' — use $facts['...'] structured fact instead (Puppet 8 / OpenVox 8)",
|
|
44
50
|
line: tok.line, column: tok.column
|
|
45
51
|
end
|
|
46
52
|
end
|
|
53
|
+
|
|
54
|
+
private
|
|
55
|
+
|
|
56
|
+
# Collect variable names that are declared locally (class/define parameters
|
|
57
|
+
# or lambda block parameters) so they are not mistaken for legacy facts.
|
|
58
|
+
def collect_local_vars
|
|
59
|
+
local = Set.new
|
|
60
|
+
toks = tokens
|
|
61
|
+
i = 0
|
|
62
|
+
while i < toks.size
|
|
63
|
+
case toks[i].type
|
|
64
|
+
when :CLASS, :DEFINE
|
|
65
|
+
# Skip to the opening LPAREN of the parameter list (stop at LBRACE)
|
|
66
|
+
j = i + 1
|
|
67
|
+
j += 1 while j < toks.size && toks[j].type != :LPAREN && toks[j].type != :LBRACE
|
|
68
|
+
if j < toks.size && toks[j].type == :LPAREN
|
|
69
|
+
depth = 1
|
|
70
|
+
j += 1
|
|
71
|
+
while j < toks.size && depth > 0
|
|
72
|
+
case toks[j].type
|
|
73
|
+
when :LPAREN then depth += 1
|
|
74
|
+
when :RPAREN then depth -= 1
|
|
75
|
+
when :VARIABLE then local << toks[j].value.sub(/^\$/, '').sub(/\A::/, '') if depth == 1
|
|
76
|
+
end
|
|
77
|
+
j += 1
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
when :PIPE
|
|
81
|
+
# Lambda parameter list: |$var1, $var2|
|
|
82
|
+
j = i + 1
|
|
83
|
+
while j < toks.size && toks[j].type != :PIPE
|
|
84
|
+
local << toks[j].value.sub(/^\$/, '').sub(/\A::/, '') if toks[j].type == :VARIABLE
|
|
85
|
+
j += 1
|
|
86
|
+
end
|
|
87
|
+
i = j # skip past closing PIPE
|
|
88
|
+
end
|
|
89
|
+
i += 1
|
|
90
|
+
end
|
|
91
|
+
local
|
|
92
|
+
end
|
|
47
93
|
end
|
|
@@ -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
|
|
@@ -13,4 +13,25 @@ OpenvoxLint.new_check(:quoted_booleans) do
|
|
|
13
13
|
line: tok.line, column: tok.column
|
|
14
14
|
end
|
|
15
15
|
end
|
|
16
|
+
|
|
17
|
+
# --fix support: unquote the boolean at the reported column (or fallback
|
|
18
|
+
# gsub). Assumes the problem points to the start of the quoted value.
|
|
19
|
+
def fix(problem)
|
|
20
|
+
idx = problem[:line] - 1
|
|
21
|
+
line = @manifest_lines[idx].dup
|
|
22
|
+
col = (problem[:column] || 1) - 1
|
|
23
|
+
if line[col] == "'" || line[col] == '"'
|
|
24
|
+
quote = line[col]
|
|
25
|
+
# Find matching closing quote for this simple boolean token
|
|
26
|
+
end_col = line.index(quote, col + 1)
|
|
27
|
+
if end_col && %w[true false].include?(line[(col + 1)...end_col])
|
|
28
|
+
val = line[(col + 1)...end_col]
|
|
29
|
+
line[col..end_col] = val
|
|
30
|
+
@manifest_lines[idx] = line
|
|
31
|
+
return
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
# Fallback (safe for most manifests)
|
|
35
|
+
@manifest_lines[idx] = line.gsub(/(['"])(true|false)\1/, '\2')
|
|
36
|
+
end
|
|
16
37
|
end
|