openvox-lint is a Ruby gem that statically analyses OpenVox / Puppet manifest
(.pp) files. It tokenises each file with a purpose-built lexer, then runs
a configurable set of check plugins against the token stream. Problems are
reported in multiple output formats suitable for humans, CI systems, and IDEs.
This document covers the architecture, every public API, every built-in check, the lexer token types, the plugin system, and integration guidance.
Version: 1.3.2
Checks: 37 built-in (with real --fix support for 5+ checks)
License: Apache 2.0
Compatibility: OpenVox 8.x, Puppet 8.x, Puppet 7.x (with deprecation warnings)
- Architecture
- API Reference
- Complete Check Reference (37 Checks)
- Token Types Reference
- Plugin Development
- Migration from puppet-lint
- File Inventory
┌─────────────┐ ┌───────┐ ┌────────┐ ┌──────────┐
│ CLI / API │────▶│ Linter │────▶│ Lexer │────▶│ Tokens │
│ │ │ │ │ │ │ (array) │
└─────────────┘ └───┬────┘ └────────┘ └────┬─────┘
│ │
│ ┌────────┐ │
└────────▶│ Checks │◀──────────┘
│ │
└───┬────┘
│
┌───▼────┐
│ Report │
└────────┘
- CLI parses command-line arguments and loads configuration
- Linter expands file arguments, reads each
.ppfile - Lexer tokenises the manifest into
Tokenobjects (doubly-linked list) - Checks runs each enabled check plugin against the token stream
- Report formats and outputs the collected problems
- Zero runtime dependencies — only Ruby standard library
- Token-based analysis — works on token stream, not AST
- Puppet/OpenVox agnostic — identical language support for both
- Extensible — plugin system for custom checks
- CI-friendly — multiple output formats, proper exit codes
The top-level namespace for all openvox-lint classes.
| Constant | Value | Description |
|---|---|---|
VERSION |
'1.3.2' |
Gem version string |
| Method | Returns | Description |
|---|---|---|
.configuration |
Configuration |
Global configuration singleton |
.configure { |c| } |
Configuration |
Yields configuration for block-style setup |
.reset_configuration! |
Configuration |
Reset configuration to defaults (called by CLI) |
.checks |
Hash{Symbol => Class} |
Registry of loaded check classes |
.new_check(name, &block) |
Class |
Register a new check plugin (warns on duplicates) |
| Exception | Inherits | Usage |
|---|---|---|
OpenvoxLint::Error |
StandardError |
General errors (syntax, unterminated strings) |
OpenvoxLint::NoFix |
StandardError |
Raised by fix() to skip unfixable problems |
require 'openvox-lint'
# Configure globally
OpenvoxLint.configure do |c|
c.fail_on_warnings = true
c.ignore_paths = ['vendor/**/*.pp']
end
# Access check registry
OpenvoxLint.checks.keys # => [:trailing_whitespace, :legacy_facts, ...]Represents a single token produced by the lexer. Tokens are linked in a doubly-linked list for easy forward/backward navigation during checks.
| Attribute | Type | Description |
|---|---|---|
type |
Symbol |
Token type (see Token Types Reference) |
value |
String |
Raw text value including quotes for strings |
line |
Integer |
1-based line number |
column |
Integer |
1-based column number |
prev_token |
Token|nil |
Previous token in doubly-linked list |
next_token |
Token|nil |
Next token in doubly-linked list |
| Method | Returns | Description |
|---|---|---|
#formatting? |
Boolean |
True if whitespace, indent, newline, or comment |
#to_s |
String |
Human-readable representation |
#inspect |
String |
Debug representation |
The following types return true for #formatting?:
:WHITESPACE— spaces/tabs not at line start:INDENT— spaces/tabs at line start:NEWLINE— line breaks:COMMENT—#comments:MLCOMMENT—/* */block comments:SLASH_COMMENT—//comments
Tokenises a Puppet/OpenVox manifest string into an array of Token objects. Recognises all Puppet 8 / OpenVox 8.x language constructs.
lexer = OpenvoxLint::Lexer.new(code_string)Raises OpenvoxLint::Error for unterminated strings, regex, or heredocs.
| Attribute | Type | Description |
|---|---|---|
tokens |
Array<Token> |
All tokens (doubly-linked) |
manifest_lines |
Array<String> |
Source lines (for line-based checks) |
- All Puppet keywords (class, define, if, case, etc.)
- Variables (
$foo,$::bar::baz) - Single and double-quoted strings with escape handling
- String interpolation (
"Hello ${name}") - Heredocs (
@("END")) - Regular expressions (
/pattern/) - All operators (
=>,->,~>,==, etc.) - Class references (
File,String,Stdlib::Absolutepath) - Numbers (decimal, hex, octal, float, scientific)
- Comments (
#,/* */,//)
Base class for all lint checks. Create new checks via OpenvoxLint.new_check.
| Method | Required | Description |
|---|---|---|
#check |
Yes | Main check logic; call notify to report problems |
#fix(problem) |
No | Auto-fix a problem; raise NoFix to skip |
| Method | Returns | Description |
|---|---|---|
tokens |
Array<Token> |
Full token stream |
manifest_lines |
Array<String> |
Source lines (0-indexed) |
semantic_tokens |
Array<Token> |
Non-formatting tokens only |
resource_indexes |
Array<Hash> |
Resource body locations with param_tokens |
class_indexes |
Array<Hash> |
Class definition locations |
defined_type_indexes |
Array<Hash> |
Defined type locations |
node_indexes |
Array<Hash> |
Node definition locations |
title_tokens |
Array<Token> |
Resource title tokens |
fullpath |
String |
Full file path being checked |
filename |
String |
Base filename |
notify(kind, details) |
— | Report a problem |
notify :warning, # or :error
message: 'description of problem',
line: 42,
column: 5{
type: Token, # Resource type token (e.g., "file")
start: Integer, # Semantic token index of opening brace
end: Integer, # Semantic token index of closing brace
param_tokens: Array # Tokens inside the resource body
}Holds all runtime configuration for a lint session.
| Attribute | Type | Default | Description |
|---|---|---|---|
log_format |
String |
'text' |
Output format |
with_filename |
Boolean |
true |
Show filename in output |
fail_on_warnings |
Boolean |
false |
Exit 1 on warnings |
fix |
Boolean |
false |
Auto-fix mode |
only_checks |
Array<Symbol> |
[] |
Run only these checks |
disabled_checks |
Array<Symbol> |
[] |
Skip these checks |
ignore_paths |
Array<String> |
['vendor/**/*.pp', 'pkg/**/*.pp', 'spec/**/*.pp'] |
Glob patterns to ignore |
relative |
Boolean |
false |
Use relative paths in output |
column |
Boolean |
true |
Show column numbers |
custom_log_format |
String|nil |
nil |
Custom format string |
| Method | Description |
|---|---|
#load_from_rc(path) |
Load config from an RC file |
#check_enabled?(name) |
Returns true if check is enabled |
# .openvox-lint.rc
# Each line is a command-line flag
--no-line_length-check
--no-documentation-check
--fail-on-warnings
--ignore-paths vendor/**/*.pp,pkg/**/*.pp
Orchestrates the linting of one or more manifest files.
linter = OpenvoxLint::Linter.new(configuration: config)| Method | Returns | Description |
|---|---|---|
#run(*paths) |
— | Lint files/directories |
#problems |
Array<Hash> |
All detected problems |
#file_count |
Integer |
Number of files checked |
#errors? |
Boolean |
Any errors found? |
#warnings? |
Boolean |
Any warnings found? |
#exit_code |
Integer |
0=clean, 1=problems |
{
path: 'manifests/init.pp',
line: 42,
column: 5,
kind: :warning, # or :error
check: :legacy_facts,
message: "legacy fact 'osfamily' — use $facts['...']"
}require 'openvox-lint'
config = OpenvoxLint::Configuration.new
config.fail_on_warnings = true
config.only_checks = [:legacy_facts, :hiera3_function]
linter = OpenvoxLint::Linter.new(configuration: config)
linter.run('manifests/')
puts "Checked #{linter.file_count} files"
puts "Found #{linter.problems.size} problems"
exit linter.exit_codeFormats and outputs lint problems in various formats.
report = OpenvoxLint::Report.new(configuration)| Method | Description |
|---|---|
#format(problems, io: $stdout) |
Format and output problems |
| Format | Flag | Description |
|---|---|---|
text |
-f text (default) |
path:line:col: KIND: check: message |
json |
-f json |
JSON array of problem objects |
csv |
-f csv |
CSV with headers |
github |
-f github |
GitHub Actions annotations |
codeclimate |
-f codeclimate |
Code Climate JSON format |
custom |
--log-format |
User-defined format string |
| Placeholder | Value |
|---|---|
%{path} |
File path |
%{line} |
Line number |
%{column} |
Column number |
%{KIND} |
Uppercase severity (WARNING/ERROR) |
%{kind} |
Lowercase severity |
%{check} |
Check name |
%{message} |
Problem message |
Detects trailing whitespace at the end of lines.
Why: Trailing whitespace is invisible noise that pollutes diffs and can cause merge conflicts.
Bad:
class foo {
ensure => present,
}Good:
class foo {
ensure => present,
}Detects hard tab characters. Puppet style requires 2-space soft tabs.
Why: Tabs render inconsistently across editors and terminals. The Puppet Style Guide mandates 2-space indentation.
Bad:
class foo {
ensure => present,
}Good:
class foo {
ensure => present,
}Lines should not exceed 140 characters.
Why: Long lines are hard to read, especially in code review and side-by-side
diffs. The check has a built-in exception for long puppet:/// URLs which
often cannot be broken.
Bad:
file { '/etc/config': content => 'This is a very long string that exceeds the maximum line length limit and makes the code hard to read in most editors and code review tools' }Good:
$content = 'This is a long string that has been assigned to a variable'
file { '/etc/config':
content => $content,
}Indentation must use 2-space increments. Flags lines with odd numbers of leading spaces.
Why: Consistent indentation improves readability. Puppet convention is exactly 2 spaces per nesting level.
Bad:
class foo {
ensure => present, # 3 spaces - odd!
}Good:
class foo {
ensure => present, # 2 spaces
}In aligned parameter blocks, only the longest key should have exactly one space
before =>. Extra spaces on the longest key indicate over-padding.
Why: When arrows are aligned, shorter keys need padding. But the longest key sets the alignment column and should have exactly one space.
Good — properly aligned:
file { '/etc/nginx/nginx.conf':
ensure => file,
content => template('nginx/nginx.conf.erb'),
owner => 'root',
group => 'root',
mode => '0644',
}Here content (7 chars) is the longest key with 1 space before =>.
Other keys have padding spaces for alignment.
Bad — longest key has extra space:
file { '/tmp/foo':
ensure => present,
mode => '0644',
}ensure is longest (6 chars) but has 2 spaces before =>.
Bad — single parameter with extra space:
package { 'httpd':
ensure => installed,
}One parameter means nothing to align with — extra spaces flagged.
Hash rockets (=>) should be aligned within a resource body. This check
groups arrows by line proximity and flags any that are not at the maximum
column position for that group.
Why: Vertical alignment improves readability of resource declarations. It makes it easy to scan parameter values at a glance.
Note: This check coordinates with space_before_arrow to avoid
contradictory warnings. If misalignment is caused by extra spaces before
the longest key's arrow, only space_before_arrow fires.
Bad — misaligned arrows:
file { '/tmp/foo':
ensure => file,
content => 'hello',
mode => '0644',
}Good — aligned arrows:
file { '/tmp/foo':
ensure => file,
content => 'hello',
mode => '0644',
}Double-quoted strings that contain no variables or escape sequences should use single quotes instead.
Exception: If the string contains literal single-quote characters
('), double quotes are correct to avoid escaping.
Why: Single quotes signal "this is a literal string" while double quotes signal "this may contain interpolation". Using single quotes when possible makes intent clearer.
Bad:
file { "/tmp/foo":
ensure => "present",
}Good:
file { '/tmp/foo':
ensure => 'present',
}Exception — nested single quotes (allowed):
notify { "It's working":
message => "Use 'ensure' as first parameter",
}A string containing only a variable should not be quoted.
Why: "${foo}" is semantically identical to $foo but adds visual noise
and suggests interpolation where none occurs.
Bad:
$result = "${some_variable}"
file { "${path}": }Good:
$result = $some_variable
file { $path: }Single-quoted strings containing $variable patterns should use double
quotes for interpolation.
Why: Variables in single-quoted strings are literal text, not interpolated. This is usually a mistake.
Bad:
notify { 'hello':
message => 'Hello $name, welcome!', # $name is literal
}Good:
notify { 'hello':
message => "Hello ${name}, welcome!", # $name is interpolated
}Variables in double-quoted strings should be enclosed in braces (${var}).
Why: Brace syntax makes variable boundaries explicit and allows array/hash
access. $foo works but ${foo} is clearer, especially with adjacent text.
Bad:
$msg = "Hello $name!"
$path = "/home/$user/bin"Good:
$msg = "Hello ${name}!"
$path = "/home/${user}/bin"Mixed handling: A string with both enclosed and unenclosed variables correctly flags only the unenclosed ones:
$msg = "Hello $name, your home is ${home}" # Only $name flaggedBoolean values true and false should not be quoted.
Why: Quoted booleans are strings, not booleans. 'true' is truthy
because it's a non-empty string, but it won't work correctly with strict
boolean comparisons or type checking.
Bad:
$enabled = 'true'
service { 'nginx': enable => "false" }Good:
$enabled = true
service { 'nginx': enable => false }Variable names must be lowercase. Names may contain underscores and colons (for namespaced variables).
Why: Puppet convention is lowercase variables. Mixed case can cause confusion with class references which are capitalised.
Bad:
$MyVariable = 'value'
$DatabasePort = 5432Good:
$my_variable = 'value'
$database_port = 5432Variable names must not contain dashes (hyphens).
Why: Dashes are not valid in Puppet variable names. The parser may interpret them as subtraction operations.
Bad:
$my-variable = 'value'Good:
$my_variable = 'value'The ensure attribute should be the first parameter in a resource body.
Why: ensure is the most important attribute — it determines whether
the resource exists. Placing it first makes resource declarations scannable.
Bad:
file { '/tmp/foo':
owner => 'root',
ensure => file,
mode => '0644',
}Good:
file { '/tmp/foo':
ensure => file,
owner => 'root',
mode => '0644',
}Symlinks should use ensure => link with a target attribute, not
ensure => '/path/to/target'.
Why: Setting ensure to a path is confusing. The explicit link +
target syntax is clearer and matches documentation examples.
Bad:
file { '/usr/local/bin/python':
ensure => '/usr/bin/python3',
}Good:
file { '/usr/local/bin/python':
ensure => link,
target => '/usr/bin/python3',
}File modes should be 4-digit quoted octal strings or symbolic modes.
Why: 3-digit modes are ambiguous (is 644 the same as 0644?).
Unquoted numbers can be misinterpreted. Symbolic modes (u+x) are allowed.
Bad:
file { '/tmp/foo':
mode => 644, # Unquoted, 3 digits
}
file { '/tmp/bar':
mode => '755', # Only 3 digits
}Good:
file { '/tmp/foo':
mode => '0644', # 4-digit quoted octal
}
file { '/tmp/bar':
mode => 'u+x', # Symbolic mode
}File modes must be quoted strings, not bare numbers.
Why: Numeric file modes are parsed as integers. 0644 is valid octal,
but 644 might be decimal. Quoting removes ambiguity.
Bad:
file { '/tmp/foo':
mode => 0644,
}Good:
file { '/tmp/foo':
mode => '0644',
}Resource titles should be quoted strings, not bare words.
Why: Bare word titles can be confused with variables or cause unexpected behaviour. Quoting makes intent explicit.
Bad:
file { tmpfile:
ensure => file,
}Good:
file { '/tmp/file':
ensure => file,
}
file { 'configuration file':
path => '/etc/app.conf',
ensure => file,
}No duplicate parameters in resource declarations.
Why: Duplicate parameters are almost always mistakes. The last value wins, which can cause subtle bugs.
Bad:
file { '/tmp/foo':
ensure => file,
owner => 'root',
owner => 'nobody', # Duplicate!
}Good:
file { '/tmp/foo':
ensure => file,
owner => 'root',
}Resource bodies should end with a trailing comma after the last attribute.
Why: Trailing commas make diffs cleaner when adding new attributes. They also prevent syntax errors when copy-pasting.
Note: Only fires inside resource bodies (name { ... }), not inside
conditionals, class bodies, or other brace contexts.
Bad:
file { '/tmp/foo':
ensure => file,
owner => 'root'
}Good:
file { '/tmp/foo':
ensure => file,
owner => 'root',
}Classes and defined types should be preceded by documentation comments.
Why: Documentation helps users understand what a class does without reading the implementation. Puppet Strings extracts these comments.
Bad:
class mymodule::webserver {
# ...
}Good:
# Configures an Nginx webserver with standard settings.
#
# @param port The port to listen on.
# @param ssl Enable SSL termination.
class mymodule::webserver (
Integer $port = 80,
Boolean $ssl = false,
) {
# ...
}Classes and defined types should not be nested inside other classes or defined types.
Why: Nested definitions are confusing and don't work as expected. Use separate files and include/contain for composition.
Bad:
class outer {
class inner { # Nested!
# ...
}
}Good:
# outer.pp
class outer {
contain outer::inner
}
# inner.pp
class outer::inner {
# ...
}Parameters without defaults should come before parameters with defaults.
Why: When calling a class, required parameters must be specified. Listing them first makes the required inputs obvious.
Bad:
class myclass (
$optional = 'default',
$required, # No default, but after one with default!
) {
}Good:
class myclass (
$required,
$optional = 'default',
) {
}Class inheritance is discouraged. Use composition (include, contain, require) instead.
Why: Class inheritance was an early Puppet feature that causes confusion. Composition is more flexible and easier to understand.
Bad:
class mymodule::child inherits mymodule::parent {
# ...
}Good:
class mymodule::child {
contain mymodule::parent
# ...
}Classes should not inherit across module namespaces.
Why: Cross-namespace inheritance creates tight coupling between modules. It makes modules harder to maintain and test independently.
Bad:
class mymodule::foo inherits othermodule::bar {
# Inheriting from a different module!
}Good:
class mymodule::foo {
contain othermodule::bar # Composition instead
}Case statements must have a default case.
Why: Without a default, unexpected values silently do nothing. Explicit defaults catch mistakes and document expected behaviour.
Bad:
case $os {
'RedHat': { include redhat }
'Debian': { include debian }
}Good:
case $os {
'RedHat': { include redhat }
'Debian': { include debian }
default: { fail("Unsupported OS: ${os}") }
}Selectors (?) should not be used inside resource declarations.
Why: Selectors inside resource bodies reduce readability. Extract the logic to a variable or use a conditional outside the resource.
Bad:
file { '/etc/app.conf':
content => $env ? {
'prod' => template('app/prod.erb'),
default => template('app/dev.erb'),
},
}Good:
$config_template = $env ? {
'prod' => 'app/prod.erb',
default => 'app/dev.erb',
}
file { '/etc/app.conf':
content => template($config_template),
}Numbers should not have leading zeros (except octal file modes).
Why: Leading zeros indicate octal notation. 010 is 8, not 10.
This is a common source of bugs.
Exception: The check allows leading zeros after mode => for file modes.
Bad:
$count = 010 # This is 8 in octal!
$port = 0080 # This is 64!Good:
$count = 10
$port = 80
file { '/tmp/foo': mode => '0644' } # Allowed for modeResource reference types must start with a capital letter.
Why: Resource references use capitalised type names: File['/tmp'],
not file['/tmp']. Lowercase looks like an array access.
Note: The check has an allowlist of 40+ functions that use bracket
syntax (each, map, filter, lookup, etc.) to avoid false positives.
Bad:
require file['/etc/config']Good:
require File['/etc/config']Class and define names should match the autoloader file path.
Why: Puppet's autoloader expects class foo::bar::baz in
foo/manifests/bar/baz.pp. Mismatches cause "class not found" errors.
Bad:
# In mymodule/manifests/init.pp
class mymodule::subclass { # Should be in subclass.pp
}Good:
# In mymodule/manifests/init.pp
class mymodule {
}
# In mymodule/manifests/subclass.pp
class mymodule::subclass {
}Use # hash comments, not /* */ block comments.
Why: Hash comments are the standard in Puppet. Block comments are inherited from C-style languages and less common in the ecosystem.
Bad:
/* This is a block comment
that spans multiple lines */
class foo {
}Good:
# This is a hash comment
# that spans multiple lines
class foo {
}puppet:/// URLs should include the /modules/ mount point.
Why: The full URL format is puppet:///modules/modulename/path.
Omitting /modules/ causes file not found errors.
Bad:
file { '/etc/config':
source => 'puppet:///mymodule/config',
}Good:
file { '/etc/config':
source => 'puppet:///modules/mymodule/config',
}Node names should be quoted strings, not bare words.
Why: Quoted names make intent explicit and avoid potential parsing issues with special characters.
Bad:
node webserver01 {
}Good:
node 'webserver01' {
}
node 'webserver01.example.com' {
}These checks are critical for users upgrading from Puppet 7 or migrating to OpenVox 8. They detect patterns that will break or behave differently in the new version.
Legacy (unstructured) top-scope facts are excluded by default in Puppet 8 / OpenVox 8. Over 80 legacy fact names are detected.
Why: Puppet 8 excludes legacy facts by default. Code using $osfamily
will break unless the agent is configured to include legacy facts (not
recommended for new code).
Bad:
if $osfamily == 'RedHat' { }
$host = $fqdn
$ip = $ipaddress
$os = $operatingsystemGood:
if $facts['os']['family'] == 'RedHat' { }
$host = $facts['networking']['fqdn']
$ip = $facts['networking']['ip']
$os = $facts['os']['name']Detected legacy facts include: architecture, bios_*, domain,
fqdn, hostname, id, interfaces, ipaddress, ipaddress6,
kernel, kernelrelease, macaddress, memoryfree, memorysize,
netmask, network, operatingsystem, operatingsystemrelease,
osfamily, processorcount, puppetversion, rubyversion,
selinux*, swapfree, swapsize, timezone, uptime*, virtual,
and many more.
Top-scope fact variables ($::factname) are deprecated. Use the $facts
hash instead.
Why: The $:: prefix was needed in older Puppet to explicitly reference
top-scope variables. Modern Puppet provides the $facts hash which is
clearer and more consistent.
Note: This check only flags simple facts, not module-qualified variables
like $::mymodule::param which are legitimate.
Bad:
$hostname = $::hostname
$os = $::operatingsystem
if $::selinux { }Good:
$hostname = $facts['networking']['hostname']
$os = $facts['os']['name']
if $facts['selinux'] { }Deprecated Hiera 3 functions (hiera, hiera_array, hiera_hash, hiera_include)
are removed in Puppet 8 / OpenVox 8. Use the Hiera 5 lookup() function instead.
Why: Hiera 3 is fully deprecated. Only Hiera 5 is supported in Puppet 8 /
OpenVox 8. The legacy hiera() functions were compatibility shims that have been
removed. This is an error, not a warning, because the code will fail immediately.
Bad — deprecated Hiera 3 functions:
$val = hiera('mykey')
$list = hiera_array('mylist')
$hash = hiera_hash('myhash')
hiera_include('classes')Good — Hiera 5 lookup() function:
$val = lookup('mykey')
$list = lookup('mylist', Array, 'unique')
$hash = lookup('myhash', Hash, 'hash')
lookup('classes', Array[String], 'unique').includeHiera 5 Features:
- Single
lookup()function replaces all Hiera 3 functions - Supports type validation:
lookup('key', String) - Supports merge strategies:
'first','unique','hash','deep' - Supports default values:
lookup('key', String, 'first', 'default_value') - Works with module-level
hiera.yamlconfiguration
The import keyword was removed in Puppet 4.
Why: This is an error because the code will fail to parse. Use module autoloading instead.
Bad:
import 'foo'
import 'nodes/*.pp'Good:
Use the module autoloader by placing files in the correct location:
modules/mymodule/manifests/init.ppforclass mymodulemodules/mymodule/manifests/subclass.ppforclass mymodule::subclass
| Type | Keyword | Type | Keyword |
|---|---|---|---|
:AND |
and |
:APPLICATION |
application |
:ATTR |
attr |
:CASE |
case |
:CLASS |
class |
:CONSUMES |
consumes |
:DEFAULT |
default |
:DEFINE |
define |
:ELSE |
else |
:ELSIF |
elsif |
:FALSE |
false |
:FUNCTION |
function |
:IF |
if |
:IMPORT |
import |
:IN |
in |
:INHERITS |
inherits |
:NODE |
node |
:NOT |
not |
:OR |
or |
:PRIVATE |
private |
:PRODUCES |
produces |
:SITE |
site |
:TRUE |
true |
:TYPE |
type |
:UNDEF |
undef |
:UNLESS |
unless |
| Type | Description | Example |
|---|---|---|
:NAME |
Identifier / bare word | ensure, myclass |
:CLASSREF |
Capitalised type reference | File, String, Stdlib::Absolutepath |
:VARIABLE |
Variable (includes $) |
$foo, $::bar::baz |
:NUMBER |
Numeric literal | 42, 0xFF, 0755, 3.14, 1e10 |
:SSTRING |
Single-quoted string | 'hello' |
:STRING |
Double-quoted string (no interpolation) | "hello" |
:DQSTRING |
Double-quoted string (with interpolation) | "hello ${name}" |
:REGEX |
Regular expression | /^foo/ |
:HEREDOC_OPEN |
Heredoc opening tag | @("END") |
:HEREDOC |
Heredoc body content | (multi-line content) |
| Type | Operator | Description |
|---|---|---|
:FARROW |
=> |
Hash rocket (parameter assignment) |
:PARROW |
+> |
Append to array attribute |
:ISEQUAL |
== |
Equality |
:NOTEQUAL |
!= |
Inequality |
:MATCH |
=~ |
Regex match |
:NOMATCH |
!~ |
Regex non-match |
:LESSEQUAL |
<= |
Less than or equal |
:GREATEREQUAL |
>= |
Greater than or equal |
:LESSTHAN |
< |
Less than |
:GREATERTHAN |
> |
Greater than |
:LSHIFT |
<< |
Left shift |
:RSHIFT |
>> |
Right shift |
:IN_EDGE |
-> |
Ordering (before) |
:OUT_EDGE |
<- |
Ordering (after) |
:IN_EDGE_SUB |
~> |
Ordering with notify |
:OUT_EDGE_SUB |
<~ |
Reverse ordering with notify |
:APPENDS |
+= |
Append assignment |
:EQUALS |
= |
Assignment |
:LCOLLECT |
<| |
Collection query open |
:RCOLLECT |
|> |
Collection query close |
:LLCOLLECT |
<<| |
Exported collection query open |
:RRCOLLECT |
|>> |
Exported collection query close |
| Type | Character | Type | Character |
|---|---|---|---|
:LBRACE |
{ |
:RBRACE |
} |
:LPAREN |
( |
:RPAREN |
) |
:LBRACK |
[ |
:RBRACK |
] |
:COMMA |
, |
:SEMIC |
; |
:DOT |
. |
:COLON |
: |
:PIPE |
| |
:AT |
@ |
:QMARK |
? |
:BACKSLASH |
\\ |
:PLUS |
+ |
:MINUS |
- |
:TIMES |
* |
:MODULO |
% |
:DIV |
/ |
:NOT |
! |
| Type | Description |
|---|---|
:WHITESPACE |
Spaces/tabs (not at line start) |
:INDENT |
Spaces/tabs at line start |
:NEWLINE |
Line break (\n or \r\n) |
:COMMENT |
Hash comment (# ...) |
:MLCOMMENT |
Multi-line comment (/* ... */) |
:SLASH_COMMENT |
C++ style comment (// ...) |
# lib/openvox-lint/plugins/checks/my_custom_check.rb
OpenvoxLint.new_check(:my_custom_check) do
def check
tokens.each do |tok|
if tok.type == :NAME && tok.value == 'deprecated_function'
notify :warning,
message: 'deprecated_function() is obsolete',
line: tok.line,
column: tok.column
end
end
end
# Optional: implement auto-fix
def fix(problem)
# Find and modify the problematic token
# Or raise NoFix if this instance can't be fixed
raise OpenvoxLint::NoFix
end
endOpenvoxLint.new_check(:resource_check) do
def check
# Check only resource bodies
resource_indexes.each do |resource|
type_name = resource[:type].value
next unless type_name == 'file'
# Examine parameters
resource[:param_tokens].each do |tok|
# Check parameter values
end
end
end
endOpenvoxLint.new_check(:line_based_check) do
def check
manifest_lines.each_with_index do |line, idx|
if line =~ /FIXME|TODO/
notify :warning,
message: 'TODO/FIXME comment found',
line: idx + 1, # Lines are 1-indexed in output
column: 1
end
end
end
end# openvox-lint-my_checks.gemspec
Gem::Specification.new do |spec|
spec.name = 'openvox-lint-my_checks'
spec.version = '1.0.0'
spec.summary = 'Custom checks for openvox-lint'
spec.add_runtime_dependency 'openvox-lint', '~> 1.0'
spec.files = Dir['lib/**/*']
spec.require_paths = ['lib']
endPlace check files in lib/openvox-lint/plugins/checks/ and they will
be auto-loaded when the gem is required.
openvox-lint is designed as a modern replacement for puppet-lint with broader compatibility and additional checks.
| Feature | puppet-lint 5.x | openvox-lint 1.x |
|---|---|---|
| Ruby requirement | ≥ 3.1 | ≥ 2.5 (works on RHEL 8, macOS system Ruby) |
| Runtime dependencies | None | None |
| Built-in checks | ~25 | 37 |
| Legacy facts detection | Via plugin | Built-in |
| Top-scope facts detection | Via plugin | Built-in |
| Deprecated Hiera 3 function detection | No | Built-in (ERROR) |
| Import statement detection | No | Built-in (ERROR) |
| Strict indent check | Via plugin | Built-in |
| GitHub Actions output | No | Built-in (-f github) |
| Code Climate output | No | Built-in (-f codeclimate) |
| CSV output | No | Built-in (-f csv) |
| OpenVox awareness | No | Yes |
--fix support |
Yes | Yes |
| Plugin system | Yes | Yes (compatible API) |
Most puppet-lint flags work identically:
# These work the same way
puppet-lint --no-documentation-check manifests/
openvox-lint --no-documentation-check manifests/
puppet-lint --only-checks legacy_facts,hiera3_function .
openvox-lint --only-checks legacy_facts,hiera3_function .
puppet-lint --log-format '%{path}:%{line}:%{KIND}' .
openvox-lint --log-format '%{path}:%{line}:%{KIND}' .Rename .puppet-lint.rc to .openvox-lint.rc. The format is identical:
# .openvox-lint.rc
--no-documentation-check
--no-line_length-check
--fail-on-warningsPlugin APIs are similar. Main differences:
- Module name:
OpenvoxLintinstead ofPuppetLint - Check registration:
OpenvoxLint.new_check(:name)instead ofPuppetLint.new_check(:name)
| Code | Meaning |
|---|---|
0 |
No errors found (warnings allowed unless --fail-on-warnings) |
1 |
Errors found, or warnings found with --fail-on-warnings |
| File | Description |
|---|---|
bin/openvox-lint |
CLI entry point (Ruby executable) |
lib/openvox-lint.rb |
Main module, auto-loads all components |
lib/openvox-lint/version.rb |
Version constant |
lib/openvox-lint/configuration.rb |
Configuration management |
lib/openvox-lint/token.rb |
Token data structure |
lib/openvox-lint/lexer.rb |
Puppet/OpenVox manifest lexer |
lib/openvox-lint/check_plugin.rb |
Base class for check plugins |
lib/openvox-lint/checks.rb |
Check runner with lint:ignore support |
lib/openvox-lint/report.rb |
Output formatters (text, json, etc.) |
lib/openvox-lint/linter.rb |
File discovery and orchestration |
lib/openvox-lint/cli.rb |
Command-line interface |
lib/openvox-lint/plugins/checks/*.rb |
37 built-in check plugins |
spec/spec_helper.rb |
RSpec test helper |
spec/unit/lexer_spec.rb |
Lexer unit tests |
spec/unit/checks_spec.rb |
Check unit tests |
openvox-lint.gemspec |
Gem specification |
Gemfile |
Development dependencies |
Rakefile |
Rake tasks |
LICENSE |
Apache 2.0 license |
README.md |
User documentation |
CHANGELOG.md |
Version history |
DOCUMENTATION.md |
This file |