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.
- checksums.yaml +7 -0
- data/CHANGELOG.md +52 -0
- data/DOCUMENTATION.md +481 -0
- data/LICENSE +83 -0
- data/README.md +497 -0
- data/bin/openvox-lint +7 -0
- data/lib/openvox-lint/check_plugin.rb +147 -0
- data/lib/openvox-lint/checks.rb +46 -0
- data/lib/openvox-lint/cli.rb +87 -0
- data/lib/openvox-lint/configuration.rb +59 -0
- data/lib/openvox-lint/lexer.rb +342 -0
- data/lib/openvox-lint/linter.rb +72 -0
- data/lib/openvox-lint/plugins/checks/arrow_alignment.rb +42 -0
- data/lib/openvox-lint/plugins/checks/autoloader_layout.rb +31 -0
- data/lib/openvox-lint/plugins/checks/case_without_default.rb +28 -0
- data/lib/openvox-lint/plugins/checks/class_inherits_params.rb +13 -0
- data/lib/openvox-lint/plugins/checks/documentation.rb +26 -0
- data/lib/openvox-lint/plugins/checks/double_quoted_strings.rb +19 -0
- data/lib/openvox-lint/plugins/checks/duplicate_params.rb +24 -0
- data/lib/openvox-lint/plugins/checks/ensure_first_param.rb +28 -0
- data/lib/openvox-lint/plugins/checks/ensure_not_symlink_target.rb +29 -0
- data/lib/openvox-lint/plugins/checks/file_mode.rb +33 -0
- data/lib/openvox-lint/plugins/checks/hard_tabs.rb +15 -0
- data/lib/openvox-lint/plugins/checks/hiera3_function.rb +16 -0
- data/lib/openvox-lint/plugins/checks/import_statement.rb +13 -0
- data/lib/openvox-lint/plugins/checks/inherits_across_namespaces.rb +27 -0
- data/lib/openvox-lint/plugins/checks/leading_zero.rb +22 -0
- data/lib/openvox-lint/plugins/checks/legacy_facts.rb +47 -0
- data/lib/openvox-lint/plugins/checks/line_length.rb +18 -0
- data/lib/openvox-lint/plugins/checks/nested_classes_or_defines.rb +26 -0
- data/lib/openvox-lint/plugins/checks/node_name_unquoted.rb +18 -0
- data/lib/openvox-lint/plugins/checks/only_variable_string.rb +18 -0
- data/lib/openvox-lint/plugins/checks/parameter_order.rb +25 -0
- data/lib/openvox-lint/plugins/checks/puppet_url_without_modules.rb +17 -0
- data/lib/openvox-lint/plugins/checks/quoted_booleans.rb +16 -0
- data/lib/openvox-lint/plugins/checks/relative_classname_inclusion.rb +24 -0
- data/lib/openvox-lint/plugins/checks/resource_reference_without_title_capital.rb +21 -0
- data/lib/openvox-lint/plugins/checks/selector_inside_resource.rb +15 -0
- data/lib/openvox-lint/plugins/checks/single_quote_string_with_variables.rb +16 -0
- data/lib/openvox-lint/plugins/checks/space_before_arrow.rb +20 -0
- data/lib/openvox-lint/plugins/checks/star_comments.rb +13 -0
- data/lib/openvox-lint/plugins/checks/strict_indent.rb +16 -0
- data/lib/openvox-lint/plugins/checks/top_scope_facts.rb +19 -0
- data/lib/openvox-lint/plugins/checks/trailing_comma.rb +24 -0
- data/lib/openvox-lint/plugins/checks/trailing_whitespace.rb +14 -0
- data/lib/openvox-lint/plugins/checks/unquoted_file_mode.rb +24 -0
- data/lib/openvox-lint/plugins/checks/unquoted_resource_title.rb +13 -0
- data/lib/openvox-lint/plugins/checks/variable_contains_dash.rb +15 -0
- data/lib/openvox-lint/plugins/checks/variable_is_lowercase.rb +16 -0
- data/lib/openvox-lint/plugins/checks/variables_not_enclosed.rb +19 -0
- data/lib/openvox-lint/report.rb +86 -0
- data/lib/openvox-lint/token.rb +38 -0
- data/lib/openvox-lint/version.rb +5 -0
- data/lib/openvox-lint.rb +47 -0
- metadata +145 -0
data/README.md
ADDED
|
@@ -0,0 +1,497 @@
|
|
|
1
|
+
# openvox-lint
|
|
2
|
+
|
|
3
|
+
**A style-guide linter for OpenVox and Puppet manifests.**
|
|
4
|
+
|
|
5
|
+
openvox-lint checks your `.pp` manifest files against the [Puppet Style Guide](https://puppet.com/docs/puppet/latest/style_guide.html) and catches common errors, deprecated patterns, legacy facts, strict-mode violations, and Puppet 8+ / OpenVox 8.x language issues.
|
|
6
|
+
|
|
7
|
+
Fully compatible with:
|
|
8
|
+
- **OpenVox 8.x** (the community-maintained open-source fork of Puppet)
|
|
9
|
+
- **Puppet 8.x**
|
|
10
|
+
- Legacy Puppet 7.x manifests (with deprecation warnings)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Table of Contents
|
|
15
|
+
|
|
16
|
+
- [Prerequisites](#prerequisites)
|
|
17
|
+
- [Installation](#installation)
|
|
18
|
+
- [Quick Start](#quick-start)
|
|
19
|
+
- [Usage](#usage)
|
|
20
|
+
- [Built-in Checks (38)](#built-in-checks)
|
|
21
|
+
- [Configuration](#configuration)
|
|
22
|
+
- [Output Formats](#output-formats)
|
|
23
|
+
- [Integration](#integration)
|
|
24
|
+
- [Writing Custom Checks](#writing-custom-checks)
|
|
25
|
+
- [Development](#development)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Prerequisites
|
|
31
|
+
|
|
32
|
+
| Requirement | Version | Notes |
|
|
33
|
+
|------------|---------|-------|
|
|
34
|
+
| **Ruby** | ≥ 3.1.0 | Required; check with `ruby --version` |
|
|
35
|
+
| **RubyGems** | ≥ 3.0 | Included with Ruby 3.1+ |
|
|
36
|
+
| **Bundler** | ≥ 2.0 | `gem install bundler` if not present |
|
|
37
|
+
| **OpenVox or Puppet** | 8.x | Optional; openvox-lint works standalone without an agent |
|
|
38
|
+
| **Git** | ≥ 2.0 | For installation from source |
|
|
39
|
+
|
|
40
|
+
### Optional Prerequisites
|
|
41
|
+
|
|
42
|
+
| Tool | Purpose |
|
|
43
|
+
|------|---------|
|
|
44
|
+
| `puppet` or `openvox` binary | For `puppet parser validate` syntax checking |
|
|
45
|
+
| `metadata-json-lint` gem | For linting module `metadata.json` files |
|
|
46
|
+
| `vim-openvox` Vim plugin | For IDE integration (async linting in Vim) |
|
|
47
|
+
| `rake` gem | For running tests during development |
|
|
48
|
+
| `rspec` gem | For running the test suite |
|
|
49
|
+
|
|
50
|
+
### Supported Platforms
|
|
51
|
+
|
|
52
|
+
- macOS (Apple Silicon and Intel)
|
|
53
|
+
- Linux (x86_64, aarch64)
|
|
54
|
+
- Windows (via Ruby for Windows)
|
|
55
|
+
|
|
56
|
+
---
|
|
57
|
+
|
|
58
|
+
## Installation
|
|
59
|
+
|
|
60
|
+
### From RubyGems (recommended)
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
gem install openvox-lint
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### From Source
|
|
67
|
+
|
|
68
|
+
```bash
|
|
69
|
+
git clone https://github.com/cvquesty/openvox-lint.git
|
|
70
|
+
cd openvox-lint
|
|
71
|
+
gem build openvox-lint.gemspec
|
|
72
|
+
gem install openvox-lint-*.gem
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Via Bundler
|
|
76
|
+
|
|
77
|
+
Add to your `Gemfile`:
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
gem 'openvox-lint'
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Then run:
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
bundle install
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Quick Start
|
|
92
|
+
|
|
93
|
+
```bash
|
|
94
|
+
# Lint a single file
|
|
95
|
+
openvox-lint manifests/init.pp
|
|
96
|
+
|
|
97
|
+
# Lint all .pp files in a directory tree
|
|
98
|
+
openvox-lint manifests/
|
|
99
|
+
|
|
100
|
+
# Lint current directory
|
|
101
|
+
openvox-lint .
|
|
102
|
+
|
|
103
|
+
# List all available checks
|
|
104
|
+
openvox-lint --list-checks
|
|
105
|
+
|
|
106
|
+
# JSON output for CI
|
|
107
|
+
openvox-lint -f json manifests/
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## Usage
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
Usage: openvox-lint [options] [file|directory ...]
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--version Display version
|
|
119
|
+
-f, --format FORMAT Output format: text, json, csv, github, codeclimate
|
|
120
|
+
--log-format FORMAT Custom log format string
|
|
121
|
+
--fix Automatically fix problems where possible
|
|
122
|
+
--fail-on-warnings Exit with error code on warnings
|
|
123
|
+
--no-filename Suppress filename in output
|
|
124
|
+
--no-column Suppress column number in output
|
|
125
|
+
--relative Display relative file paths
|
|
126
|
+
--only-checks CHECKS Comma-separated list of checks to run
|
|
127
|
+
--ignore-paths PATHS Comma-separated list of glob patterns to ignore
|
|
128
|
+
--list-checks List all available checks
|
|
129
|
+
-c, --config FILE Path to configuration file
|
|
130
|
+
--no-<check_name>-check Disable a specific check
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Examples
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
# Disable specific checks
|
|
137
|
+
openvox-lint --no-line_length-check --no-arrow_alignment-check .
|
|
138
|
+
|
|
139
|
+
# Run only specific checks
|
|
140
|
+
openvox-lint --only-checks legacy_facts,hiera3_function,top_scope_facts .
|
|
141
|
+
|
|
142
|
+
# Custom log format (compatible with puppet-lint)
|
|
143
|
+
openvox-lint --log-format '%{path}:%{line}:%{column}:%{KIND}:%{check}:%{message}' .
|
|
144
|
+
|
|
145
|
+
# GitHub Actions annotations
|
|
146
|
+
openvox-lint -f github manifests/
|
|
147
|
+
|
|
148
|
+
# Fail CI on warnings too
|
|
149
|
+
openvox-lint --fail-on-warnings manifests/
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## Built-in Checks
|
|
155
|
+
|
|
156
|
+
openvox-lint ships with **38 built-in checks** organized into categories:
|
|
157
|
+
|
|
158
|
+
### Whitespace & Formatting (5 checks)
|
|
159
|
+
|
|
160
|
+
| Check | Severity | Description |
|
|
161
|
+
|-------|----------|-------------|
|
|
162
|
+
| `trailing_whitespace` | warning | No trailing whitespace at end of lines |
|
|
163
|
+
| `hard_tabs` | warning | Use 2-space soft tabs, not literal tabs |
|
|
164
|
+
| `line_length` | warning | Lines should not exceed 140 characters |
|
|
165
|
+
| `space_before_arrow` | warning | At most one space before `=>` |
|
|
166
|
+
| `strict_indent` | warning | Indentation must use 2-space increments |
|
|
167
|
+
|
|
168
|
+
### Arrow Alignment (1 check)
|
|
169
|
+
|
|
170
|
+
| Check | Severity | Description |
|
|
171
|
+
|-------|----------|-------------|
|
|
172
|
+
| `arrow_alignment` | warning | `=>` arrows should align within resource bodies |
|
|
173
|
+
|
|
174
|
+
### Quoting & Strings (5 checks)
|
|
175
|
+
|
|
176
|
+
| Check | Severity | Description |
|
|
177
|
+
|-------|----------|-------------|
|
|
178
|
+
| `double_quoted_strings` | warning | Use single quotes for strings without interpolation |
|
|
179
|
+
| `only_variable_string` | warning | Don't quote strings containing only a variable |
|
|
180
|
+
| `single_quote_string_with_variables` | warning | Use double quotes for strings with variables |
|
|
181
|
+
| `variables_not_enclosed` | warning | Variables in strings must use `${var}` braces |
|
|
182
|
+
| `quoted_booleans` | warning | Don't quote boolean values `true`/`false` |
|
|
183
|
+
|
|
184
|
+
### Variables (2 checks)
|
|
185
|
+
|
|
186
|
+
| Check | Severity | Description |
|
|
187
|
+
|-------|----------|-------------|
|
|
188
|
+
| `variable_is_lowercase` | warning | All variables must be lowercase |
|
|
189
|
+
| `variable_contains_dash` | warning | Variables must not contain dashes |
|
|
190
|
+
|
|
191
|
+
### Resources (7 checks)
|
|
192
|
+
|
|
193
|
+
| Check | Severity | Description |
|
|
194
|
+
|-------|----------|-------------|
|
|
195
|
+
| `ensure_first_param` | warning | `ensure` must be the first attribute |
|
|
196
|
+
| `ensure_not_symlink_target` | warning | Use `ensure => link` with `target` |
|
|
197
|
+
| `file_mode` | warning | File modes as 4-digit quoted octal or symbolic |
|
|
198
|
+
| `unquoted_file_mode` | warning | File modes must be quoted strings |
|
|
199
|
+
| `unquoted_resource_title` | warning | Resource titles must be quoted |
|
|
200
|
+
| `duplicate_params` | error | No duplicate parameters in resources |
|
|
201
|
+
| `trailing_comma` | warning | Trailing comma after last attribute |
|
|
202
|
+
|
|
203
|
+
### Classes & Defines (5 checks)
|
|
204
|
+
|
|
205
|
+
| Check | Severity | Description |
|
|
206
|
+
|-------|----------|-------------|
|
|
207
|
+
| `documentation` | warning | Classes/defines must have preceding documentation |
|
|
208
|
+
| `nested_classes_or_defines` | warning | No nesting of classes or defines |
|
|
209
|
+
| `parameter_order` | warning | Params without defaults before params with defaults |
|
|
210
|
+
| `class_inherits_params` | warning | Avoid class inheritance; use composition |
|
|
211
|
+
| `inherits_across_namespaces` | warning | No inheritance across module namespaces |
|
|
212
|
+
|
|
213
|
+
### Conditionals (2 checks)
|
|
214
|
+
|
|
215
|
+
| Check | Severity | Description |
|
|
216
|
+
|-------|----------|-------------|
|
|
217
|
+
| `case_without_default` | warning | Case statements must have a `default` case |
|
|
218
|
+
| `selector_inside_resource` | warning | Don't use selectors inside resource bodies |
|
|
219
|
+
|
|
220
|
+
### References & Syntax (4 checks)
|
|
221
|
+
|
|
222
|
+
| Check | Severity | Description |
|
|
223
|
+
|-------|----------|-------------|
|
|
224
|
+
| `leading_zero` | warning | No leading zeros in numbers (except file modes) |
|
|
225
|
+
| `resource_reference_without_title_capital` | warning | Capitalise resource reference types |
|
|
226
|
+
| `relative_classname_inclusion` | warning | Use fully qualified class names |
|
|
227
|
+
| `autoloader_layout` | warning | Class/define name must match file path |
|
|
228
|
+
|
|
229
|
+
### Comments (1 check)
|
|
230
|
+
|
|
231
|
+
| Check | Severity | Description |
|
|
232
|
+
|-------|----------|-------------|
|
|
233
|
+
| `star_comments` | warning | Use `#` comments, not `/* */` |
|
|
234
|
+
|
|
235
|
+
### URLs (1 check)
|
|
236
|
+
|
|
237
|
+
| Check | Severity | Description |
|
|
238
|
+
|-------|----------|-------------|
|
|
239
|
+
| `puppet_url_without_modules` | warning | `puppet:///` URLs must include `/modules/` |
|
|
240
|
+
|
|
241
|
+
### Nodes (1 check)
|
|
242
|
+
|
|
243
|
+
| Check | Severity | Description |
|
|
244
|
+
|-------|----------|-------------|
|
|
245
|
+
| `node_name_unquoted` | warning | Node names must be quoted strings |
|
|
246
|
+
|
|
247
|
+
### Puppet 8 / OpenVox 8 Compatibility (4 checks) ⭐ NEW
|
|
248
|
+
|
|
249
|
+
| Check | Severity | Description |
|
|
250
|
+
|-------|----------|-------------|
|
|
251
|
+
| `legacy_facts` | warning | Legacy facts removed in Puppet 8; use `$facts['...']` |
|
|
252
|
+
| `top_scope_facts` | warning | `$::fact` references; use `$facts['fact']` |
|
|
253
|
+
| `hiera3_function` | **error** | `hiera()` / `hiera_hash()` removed; use `lookup()` |
|
|
254
|
+
| `import_statement` | **error** | `import` removed in Puppet 4+ |
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Configuration
|
|
259
|
+
|
|
260
|
+
### RC File
|
|
261
|
+
|
|
262
|
+
Create `.openvox-lint.rc` in your project root or `~/.openvox-lint.rc`:
|
|
263
|
+
|
|
264
|
+
```
|
|
265
|
+
# Disable specific checks
|
|
266
|
+
--no-line_length-check
|
|
267
|
+
--no-strict_indent-check
|
|
268
|
+
|
|
269
|
+
# Only run specific checks
|
|
270
|
+
# --only-checks legacy_facts,hiera3_function
|
|
271
|
+
|
|
272
|
+
# Fail on warnings in CI
|
|
273
|
+
--fail-on-warnings
|
|
274
|
+
|
|
275
|
+
# Ignore paths
|
|
276
|
+
--ignore-paths vendor/**/*.pp,pkg/**/*.pp
|
|
277
|
+
```
|
|
278
|
+
|
|
279
|
+
### Inline Ignore Comments
|
|
280
|
+
|
|
281
|
+
Suppress checks on specific lines:
|
|
282
|
+
|
|
283
|
+
```puppet
|
|
284
|
+
# lint:ignore:line_length
|
|
285
|
+
$very_long_variable_name = 'a very long value that exceeds the line length limit because it is a very descriptive configuration string'
|
|
286
|
+
# lint:endignore
|
|
287
|
+
|
|
288
|
+
class foo { # lint:ignore:documentation
|
|
289
|
+
}
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
---
|
|
293
|
+
|
|
294
|
+
## Output Formats
|
|
295
|
+
|
|
296
|
+
### Text (default)
|
|
297
|
+
|
|
298
|
+
```
|
|
299
|
+
manifests/init.pp:5:15: WARNING: unquoted_file_mode: unquoted file mode
|
|
300
|
+
manifests/init.pp:9:3: ERROR: hiera3_function: 'hiera()' is removed in Puppet 8
|
|
301
|
+
```
|
|
302
|
+
|
|
303
|
+
### JSON (`-f json`)
|
|
304
|
+
|
|
305
|
+
```json
|
|
306
|
+
[
|
|
307
|
+
{
|
|
308
|
+
"path": "manifests/init.pp",
|
|
309
|
+
"line": 5,
|
|
310
|
+
"column": 15,
|
|
311
|
+
"kind": "warning",
|
|
312
|
+
"check": "unquoted_file_mode",
|
|
313
|
+
"message": "unquoted file mode"
|
|
314
|
+
}
|
|
315
|
+
]
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### GitHub Actions (`-f github`)
|
|
319
|
+
|
|
320
|
+
```
|
|
321
|
+
::warning file=manifests/init.pp,line=5,col=15::unquoted_file_mode: unquoted file mode
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### CSV (`-f csv`)
|
|
325
|
+
|
|
326
|
+
```
|
|
327
|
+
path,line,column,kind,check,message
|
|
328
|
+
manifests/init.pp,5,15,warning,unquoted_file_mode,"unquoted file mode"
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Code Climate (`-f codeclimate`)
|
|
332
|
+
|
|
333
|
+
Standard Code Climate JSON format for quality dashboards.
|
|
334
|
+
|
|
335
|
+
### Custom (`--log-format`)
|
|
336
|
+
|
|
337
|
+
```bash
|
|
338
|
+
openvox-lint --log-format '%{path}:%{line}:%{column}:%{KIND}:%{check}:%{message}' .
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Available placeholders: `%{path}`, `%{line}`, `%{column}`, `%{KIND}`, `%{kind}`, `%{check}`, `%{message}`
|
|
342
|
+
|
|
343
|
+
---
|
|
344
|
+
|
|
345
|
+
## Integration
|
|
346
|
+
|
|
347
|
+
### vim-openvox
|
|
348
|
+
|
|
349
|
+
openvox-lint is the default backend for the [vim-openvox](https://github.com/cvquesty/vim-openvox) Vim plugin. Set in `.vimrc`:
|
|
350
|
+
|
|
351
|
+
```vim
|
|
352
|
+
let g:openvox_lint_command = 'openvox-lint'
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
### GitHub Actions
|
|
356
|
+
|
|
357
|
+
```yaml
|
|
358
|
+
name: Lint
|
|
359
|
+
on: [push, pull_request]
|
|
360
|
+
jobs:
|
|
361
|
+
lint:
|
|
362
|
+
runs-on: ubuntu-latest
|
|
363
|
+
steps:
|
|
364
|
+
- uses: actions/checkout@v4
|
|
365
|
+
- uses: ruby/setup-ruby@v1
|
|
366
|
+
with:
|
|
367
|
+
ruby-version: '3.2'
|
|
368
|
+
- run: gem install openvox-lint
|
|
369
|
+
- run: openvox-lint --fail-on-warnings -f github manifests/
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
### Rake Integration
|
|
373
|
+
|
|
374
|
+
```ruby
|
|
375
|
+
require 'openvox-lint'
|
|
376
|
+
|
|
377
|
+
task :lint do
|
|
378
|
+
linter = OpenvoxLint::Linter.new
|
|
379
|
+
linter.run('manifests/')
|
|
380
|
+
OpenvoxLint::Report.new(OpenvoxLint.configuration).format(linter.problems)
|
|
381
|
+
exit linter.exit_code
|
|
382
|
+
end
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
### Pre-commit Hook
|
|
386
|
+
|
|
387
|
+
```bash
|
|
388
|
+
#!/bin/bash
|
|
389
|
+
# .git/hooks/pre-commit
|
|
390
|
+
files=$(git diff --cached --name-only --diff-filter=ACM | grep '\.pp$')
|
|
391
|
+
[ -z "$files" ] && exit 0
|
|
392
|
+
openvox-lint --fail-on-warnings $files
|
|
393
|
+
```
|
|
394
|
+
|
|
395
|
+
---
|
|
396
|
+
|
|
397
|
+
## Writing Custom Checks
|
|
398
|
+
|
|
399
|
+
Create a Ruby file with a check plugin:
|
|
400
|
+
|
|
401
|
+
```ruby
|
|
402
|
+
# lib/openvox-lint/plugins/checks/my_custom_check.rb
|
|
403
|
+
OpenvoxLint.new_check(:my_custom_check) do
|
|
404
|
+
def check
|
|
405
|
+
tokens.each do |tok|
|
|
406
|
+
if tok.type == :NAME && tok.value == 'something_bad'
|
|
407
|
+
notify :warning,
|
|
408
|
+
message: 'found something bad',
|
|
409
|
+
line: tok.line,
|
|
410
|
+
column: tok.column
|
|
411
|
+
end
|
|
412
|
+
end
|
|
413
|
+
end
|
|
414
|
+
end
|
|
415
|
+
```
|
|
416
|
+
|
|
417
|
+
Place in `lib/openvox-lint/plugins/checks/` and it will be auto-loaded.
|
|
418
|
+
|
|
419
|
+
---
|
|
420
|
+
|
|
421
|
+
## Development
|
|
422
|
+
|
|
423
|
+
```bash
|
|
424
|
+
git clone https://github.com/cvquesty/openvox-lint.git
|
|
425
|
+
cd openvox-lint
|
|
426
|
+
bundle install
|
|
427
|
+
bundle exec rake spec
|
|
428
|
+
```
|
|
429
|
+
|
|
430
|
+
### Project Structure
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
openvox-lint/
|
|
434
|
+
├── bin/
|
|
435
|
+
│ └── openvox-lint # CLI executable
|
|
436
|
+
├── lib/
|
|
437
|
+
│ ├── openvox-lint.rb # Main entry point
|
|
438
|
+
│ └── openvox-lint/
|
|
439
|
+
│ ├── version.rb # Version constant
|
|
440
|
+
│ ├── configuration.rb # Configuration management
|
|
441
|
+
│ ├── token.rb # Token data structure
|
|
442
|
+
│ ├── lexer.rb # Puppet/OpenVox lexer/tokenizer
|
|
443
|
+
│ ├── check_plugin.rb # Base class for checks
|
|
444
|
+
│ ├── checks.rb # Check runner/orchestrator
|
|
445
|
+
│ ├── report.rb # Output formatters
|
|
446
|
+
│ ├── linter.rb # File-level orchestrator
|
|
447
|
+
│ ├── cli.rb # Command-line interface
|
|
448
|
+
│ └── plugins/
|
|
449
|
+
│ └── checks/ # 38 built-in check plugins
|
|
450
|
+
│ ├── trailing_whitespace.rb
|
|
451
|
+
│ ├── legacy_facts.rb
|
|
452
|
+
│ ├── hiera3_function.rb
|
|
453
|
+
│ └── ...
|
|
454
|
+
├── spec/ # RSpec test suite
|
|
455
|
+
├── openvox-lint.gemspec
|
|
456
|
+
├── Gemfile
|
|
457
|
+
├── Rakefile
|
|
458
|
+
├── LICENSE # Apache 2.0
|
|
459
|
+
├── README.md
|
|
460
|
+
├── CHANGELOG.md
|
|
461
|
+
└── DOCUMENTATION.md
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
---
|
|
465
|
+
|
|
466
|
+
## Comparison with puppet-lint
|
|
467
|
+
|
|
468
|
+
| Feature | puppet-lint 5.x | openvox-lint 1.0 |
|
|
469
|
+
|---------|-----------------|------------------|
|
|
470
|
+
| Ruby requirement | ≥ 3.1 | ≥ 3.1 |
|
|
471
|
+
| Runtime dependencies | None | None |
|
|
472
|
+
| Built-in checks | ~25 | 38 |
|
|
473
|
+
| Legacy facts detection | Via plugin | Built-in |
|
|
474
|
+
| Top-scope facts detection | Via plugin | Built-in |
|
|
475
|
+
| Hiera 3 detection | No | Built-in (error) |
|
|
476
|
+
| Import statement detection | No | Built-in (error) |
|
|
477
|
+
| Strict indent check | Via plugin | Built-in |
|
|
478
|
+
| GitHub Actions output | No | Built-in (`-f github`) |
|
|
479
|
+
| Code Climate output | No | Built-in (`-f codeclimate`) |
|
|
480
|
+
| CSV output | No | Built-in (`-f csv`) |
|
|
481
|
+
| Custom log format | Yes | Yes (compatible) |
|
|
482
|
+
| OpenVox awareness | No | Yes |
|
|
483
|
+
| Plugin system | Yes | Yes (compatible) |
|
|
484
|
+
| `--fix` support | Yes | Yes |
|
|
485
|
+
| vim-openvox integration | No | Native |
|
|
486
|
+
|
|
487
|
+
---
|
|
488
|
+
|
|
489
|
+
## License
|
|
490
|
+
|
|
491
|
+
Apache License, Version 2.0. See [LICENSE](LICENSE) for details.
|
|
492
|
+
|
|
493
|
+
---
|
|
494
|
+
|
|
495
|
+
## Author
|
|
496
|
+
|
|
497
|
+
Johnny Sheets ([@cvquesty](https://github.com/cvquesty))
|
data/bin/openvox-lint
ADDED
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenvoxLint
|
|
4
|
+
# Base class for every lint check. Subclass via OpenvoxLint.new_check.
|
|
5
|
+
class CheckPlugin
|
|
6
|
+
attr_reader :problems
|
|
7
|
+
|
|
8
|
+
class << self
|
|
9
|
+
attr_reader :check_name
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@problems = []
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def run(tokens:, manifest_lines:, fullpath:, ignore_comments: [])
|
|
17
|
+
@tokens = tokens
|
|
18
|
+
@manifest_lines = manifest_lines
|
|
19
|
+
@fullpath = fullpath
|
|
20
|
+
@path = fullpath
|
|
21
|
+
@filename = File.basename(fullpath)
|
|
22
|
+
@problems = []
|
|
23
|
+
@ignore_comments = ignore_comments
|
|
24
|
+
check
|
|
25
|
+
@problems.reject! { |p| ignored?(p) }
|
|
26
|
+
@problems
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def fix_problems
|
|
30
|
+
@problems.each do |problem|
|
|
31
|
+
fix(problem)
|
|
32
|
+
rescue OpenvoxLint::NoFix
|
|
33
|
+
next
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
protected
|
|
38
|
+
|
|
39
|
+
def check
|
|
40
|
+
raise NotImplementedError, "#{self.class} must implement #check"
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def fix(_problem)
|
|
44
|
+
raise OpenvoxLint::NoFix
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
attr_reader :tokens, :manifest_lines, :fullpath, :path, :filename
|
|
48
|
+
|
|
49
|
+
def notify(kind, details)
|
|
50
|
+
details[:kind] = kind
|
|
51
|
+
details[:check] = self.class.check_name
|
|
52
|
+
details[:path] = @path
|
|
53
|
+
@problems << details
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def semantic_tokens
|
|
57
|
+
tokens.reject(&:formatting?)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def resource_indexes
|
|
61
|
+
@resource_indexes ||= compute_resource_indexes
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def class_indexes
|
|
65
|
+
@class_indexes ||= find_keyword_indexes(:CLASS)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def defined_type_indexes
|
|
69
|
+
@defined_type_indexes ||= find_keyword_indexes(:DEFINE)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def node_indexes
|
|
73
|
+
@node_indexes ||= find_keyword_indexes(:NODE)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def title_tokens
|
|
77
|
+
@title_tokens ||= compute_title_tokens
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def ignored?(problem)
|
|
83
|
+
return false if @ignore_comments.empty?
|
|
84
|
+
line = problem[:line]
|
|
85
|
+
@ignore_comments.any? do |ic|
|
|
86
|
+
ic[:line] == line && (ic[:checks].empty? || ic[:checks].include?(problem[:check].to_s))
|
|
87
|
+
end
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def compute_resource_indexes
|
|
91
|
+
results = []; i = 0; sem = semantic_tokens
|
|
92
|
+
while i < sem.length
|
|
93
|
+
if sem[i].type == :NAME && i + 1 < sem.length && sem[i + 1].type == :LBRACE
|
|
94
|
+
rtype = sem[i]; brace = i + 1; depth = 1; j = brace + 1; params = []
|
|
95
|
+
while j < sem.length && depth > 0
|
|
96
|
+
case sem[j].type
|
|
97
|
+
when :LBRACE then depth += 1
|
|
98
|
+
when :RBRACE then depth -= 1
|
|
99
|
+
end
|
|
100
|
+
params << sem[j] if depth > 0
|
|
101
|
+
j += 1
|
|
102
|
+
end
|
|
103
|
+
results << { type: rtype, start: brace, end: j - 1, param_tokens: params }
|
|
104
|
+
i = j
|
|
105
|
+
else
|
|
106
|
+
i += 1
|
|
107
|
+
end
|
|
108
|
+
end
|
|
109
|
+
results
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def find_keyword_indexes(keyword)
|
|
113
|
+
results = []; sem = semantic_tokens
|
|
114
|
+
sem.each_with_index do |tok, i|
|
|
115
|
+
next unless tok.type == keyword
|
|
116
|
+
j = i + 1
|
|
117
|
+
j += 1 while j < sem.length && sem[j].type != :LBRACE
|
|
118
|
+
next if j >= sem.length
|
|
119
|
+
brace_start = j; depth = 1; j += 1
|
|
120
|
+
while j < sem.length && depth > 0
|
|
121
|
+
case sem[j].type
|
|
122
|
+
when :LBRACE then depth += 1
|
|
123
|
+
when :RBRACE then depth -= 1
|
|
124
|
+
end
|
|
125
|
+
j += 1
|
|
126
|
+
end
|
|
127
|
+
params = sem[(i + 1)...brace_start]
|
|
128
|
+
results << {
|
|
129
|
+
start: i, end: j - 1, tokens: sem[i...j],
|
|
130
|
+
param_tokens: params, name_token: sem[i + 1],
|
|
131
|
+
}
|
|
132
|
+
end
|
|
133
|
+
results
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def compute_title_tokens
|
|
137
|
+
results = []; sem = semantic_tokens
|
|
138
|
+
sem.each_with_index do |tok, i|
|
|
139
|
+
if tok.type == :LBRACE && i > 0 && sem[i - 1].type == :NAME
|
|
140
|
+
j = i + 1
|
|
141
|
+
results << sem[j] if j < sem.length && %i[SSTRING STRING NAME VARIABLE].include?(sem[j].type)
|
|
142
|
+
end
|
|
143
|
+
end
|
|
144
|
+
results
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
end
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenvoxLint
|
|
4
|
+
# Runs all enabled checks against a tokenised manifest.
|
|
5
|
+
class Checks
|
|
6
|
+
attr_reader :problems
|
|
7
|
+
|
|
8
|
+
def initialize(tokens:, manifest_lines:, fullpath:, configuration:)
|
|
9
|
+
@tokens = tokens
|
|
10
|
+
@manifest_lines = manifest_lines
|
|
11
|
+
@fullpath = fullpath
|
|
12
|
+
@configuration = configuration
|
|
13
|
+
@problems = []
|
|
14
|
+
@ignore_comments = parse_ignore_comments
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def run
|
|
18
|
+
OpenvoxLint.checks.each do |name, klass|
|
|
19
|
+
next unless @configuration.check_enabled?(name)
|
|
20
|
+
plugin = klass.new
|
|
21
|
+
results = plugin.run(
|
|
22
|
+
tokens: @tokens, manifest_lines: @manifest_lines,
|
|
23
|
+
fullpath: @fullpath, ignore_comments: @ignore_comments,
|
|
24
|
+
)
|
|
25
|
+
@problems.concat(results)
|
|
26
|
+
plugin.fix_problems if @configuration.fix && plugin.respond_to?(:fix_problems)
|
|
27
|
+
end
|
|
28
|
+
@problems.sort_by { |p| [p[:line] || 0, p[:column] || 0] }
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
private
|
|
32
|
+
|
|
33
|
+
def parse_ignore_comments
|
|
34
|
+
results = []
|
|
35
|
+
@tokens.each do |tok|
|
|
36
|
+
next unless tok.type == :COMMENT
|
|
37
|
+
if tok.value =~ /lint:ignore:(.+)/
|
|
38
|
+
results << { line: tok.line, checks: Regexp.last_match(1).strip.split(/\s*,\s*/) }
|
|
39
|
+
elsif tok.value =~ /lint:ignore\b/
|
|
40
|
+
results << { line: tok.line, checks: [] }
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
results
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|