Perl Live Linter
Live diagnostics for .pl, .pm, .t, and untitled Perl documents, including unsaved edits. Hover over an underline or open the Problems panel for details.
Rules
| Rule |
Appearance |
Behavior |
| Undeclared variable |
Error underline |
Flags a use without a preceding declaration in the current or enclosing scope. |
| Unused variable |
Warning underline |
Flags lexical variables and named parameters that are never read. Plain assignments, including element and list assignments, do not count as reads. |
| Shadowed variable |
Warning underline |
Flags a declaration that hides an enclosing declaration; includes a link to the original declaration. |
| Redeclared variable |
Warning underline |
Flags repeated declarations in the same scope, including duplicate names in a declaration list. |
| Syntax error |
Error underline |
Detects unmatched delimiters, unterminated quotes and heredocs, missing operands, and common missing-semicolon cases. |
use feature 'signatures';
sub greet($name, $_ignored = undef) {
my $unused = 42; # Warning: never read
print "Hello, $name"; # Signature parameter is recognized
{
my $name = 'Other'; # Warning: hides the parameter
print $name;
}
print $naem; # Warning: undeclared variable
}
Prefix an intentionally unused variable or parameter with _ (for example, $_ignored). Package variables declared with our and imported variables are exempt from unused checks because other files may read them. Reads in nested scopes, closures, interpolation, and default parameter expressions count. Compound assignments and increments count as reads; the rule does not perform whole-program data-flow analysis or report individual unused assignments.
Run locally
- Open this folder in VS Code.
- Press F5 and select Run Perl Live Linter if prompted.
- In the Extension Development Host, open
examples/features.pl, examples/syntax-errors.pl, or another Perl file. Diagnostics update while typing.
To install in your regular VS Code editor, package and install from this directory:
npx @vscode/vsce package
code --install-extension perl-live-linter-0.5.0.vsix --force
Reload the VS Code window after replacing a previously installed version.
The extension itself requires no build step, npm dependencies, or Perl installation. It reads document text and never runs the file, loads modules, or invokes perl -c (which can execute compile-time code).
Hash, array, and scalar checks
For example, this initializer gets a red underline:
my %hash = [key1 => 123];
print $hash{key1};
[...] creates one array reference. A hash assignment expects a list of key/value pairs. The second line uses valid hash-access syntax; fix the initializer:
my %hash = (key1 => 123);
print $hash{key1};
Or use a scalar holding a hash reference:
my $hash = {key1 => 123};
print $hash->{key1};
The checks cover simple declarations and reassignments:
| Rule |
Default |
Checks |
hash-initialization |
Error |
A single reference assigned to a hash, or a statically known odd number of hash items. |
array-initialization |
Error |
A bare reference assigned to an array. This is legal Perl but creates a one-element array. An explicit list such as ([1, 2]) is accepted as intentional. |
scalar-list-assignment |
Warning |
A statically recognizable multi-item parenthesized expression assigned in scalar context. Scalars do not store lists; list destructuring and array-length assignments are accepted. |
collection-access |
Error |
Confusion between a declared array, hash, and scalar of the same name, including missing or unnecessary ->. |
reference-type |
Error |
A directly known array/hash/scalar reference accessed with incompatible ->[...] or ->{...} syntax. |
Each diagnostic explains the difference and suggests the appropriate Perl syntax. Try examples/collections.pl.
These are conservative checks, not a runtime type system. Function return types, unknown list expansion, complex expressions, and nested reference contents are not inferred. Reference assumptions are discarded across control-flow boundaries and unknown calls; references are tracked only in simple straight-line code. The checker does not claim that every legal but suspicious expression is an error.
Duplicate hash keys
Literal hash lists and hash references warn when a key is repeated:
my %person = (name => 'Alice', name => 'Bob'); # Duplicate key: name
my $options = { timeout => 10, timeout => 30 }; # Duplicate key: timeout
The warning links to the first occurrence. Bareword keys before =>, simple unescaped single/double-quoted keys, and ordinary nonnegative decimal integer keys are compared. Nested hash references are checked independently. Unknown list expansion and complex expressions are skipped; interpolated or escaped string keys are not evaluated. Ordinary array lists and function argument lists are not checked.
Configure perlLiveLinter.rules.duplicate-hash-key or suppress a warning with # perl-ignore duplicate.
Completion and subroutine outline
- Type
$, @, or % to suggest declared variables visible at the cursor. Suggestions respect declaration order, shadowing, signatures, and scalar/array/hash namespaces, including element accesses and $#array syntax. Completion replaces only the name, preserving sigils, braces, and any following index.
- Named subroutines and packages appear in Outline and Go to Symbol in Editor (
Ctrl+Shift+O). Subroutine entries show their package-qualified names and select the declaration name.
- F12, Ctrl-click, and Go to Declaration also resolve explicit local subroutine calls such as
greet(), &greet, and Example::greet(). Forward declarations resolve to a unique body when available.
These features use the current document, including unsaved edits, and remain available when diagnostics are disabled. Completion covers tracked declarations, not module exports or dynamically created variables. Subroutine navigation does not infer method dispatch, bareword calls without parentheses, lexical subs, or definitions in other files. Multiple competing bodies are left unresolved.
Navigation, rename, and typo fixes
- F12 or Ctrl-click a variable to jump to its declaration. Go to Declaration is also available from the context menu. These resolve declarations in the current document, including parameters and the collections behind element accesses.
- F2 renames the selected lexical variable or parameter and its bound references in the current document. Enter the name without
$, @, or %. Sigils, braces, slices, and interpolation are preserved. Comments and literal strings are left alone.
- Ctrl+. on an undeclared variable offers up to three nearby spellings from declarations visible at that position. Suggestions stay in the same scalar/array/hash namespace. Selecting a suggestion changes that occurrence only.
Rename checks the proposed text again and rejects collisions that change reference bindings, including capture of an existing unresolved reference. It also rejects same-scope name collisions, Perl special names, package/imported variables, and files with detected syntax errors. Because this is a partial parser, rename is conservatively unavailable in files containing eval, no strict, or declarations inside embedded interpolation. It does not rename external symbols or dynamically constructed names. Review VS Code's rename preview when using syntax outside the documented support.
Navigation and rename remain available when diagnostics are disabled. Typo fixes respect disabled rules and suppression comments.
Inline suppression
Use actual Perl comments to suppress selected diagnostics on the same line or the immediately following physical line:
my $unused; # perl-ignore unused
# perl-ignore-next undeclared -- provided by the framework
print $external;
my $temporary; print $external; # perl-ignore unused, undeclared
Use all (or omit the rule list) to suppress every rule for that line. Suppression uses the line where the diagnostic starts; for an unused variable, that is the declaration line. Short rule names are unused, undeclared, shadowed, redeclared, syntax, hash, array, scalar, access, reference, and duplicate. Full rule names and the older perl-linter-disable-line / perl-linter-disable-next-line comments also remain supported. Unknown rule names suppress nothing. Directives inside strings, regexes, POD, and heredoc text do not suppress diagnostics. Suppression hides diagnostics without changing scope analysis or rename collision checks.
Perl support
my, our, and state, including declaration lists, declaration order, nested scopes, loops, and conditional branches.
- Named and anonymous subroutine signatures, including defaults referring to earlier parameters and slurpy array/hash parameters. Traditional
my (...) = @_ arguments and ordinary prototypes are also recognized. Signature recognition does not validate which Perl version/features the file enables.
- Explicit variable imports, such as
use Config qw(%Config) or use Example '$value', and use vars qw($value @items). Names are tracked per package; package-qualified variables are allowed, including legacy apostrophe separators. Explicit imports are assumed to be provided by the named module, without inspecting or executing it.
- Separate scalar, array, and hash namespaces; element/slice accesses; common prefix/postfix reference dereferences.
- Variable interpolation in strings, regexes and heredocs, including indexed variables and embedded expressions such as
"@{[ $value->method() ]}". Heredoc initializer references use the declaration visibility at the initializer.
- Comments, POD, literal quote forms, and data/end sections are excluded from code checks. Perl special variables are allowed.
local does not introduce a lexical declaration.
Limits
This remains a tolerant static checker, not a full Perl compiler. Syntax diagnostics cover the cases above, not every syntax error. Context-sensitive bareword calls, regex-versus-division ambiguities, unparenthesized control conditions, unusual prototypes/attributes, and complex interpolated code can still be misinterpreted. Imported variables supplied through default exports, export tags, computed import lists or dynamic symbol manipulation cannot be inferred. Declarations inside embedded interpolation expressions are not fully scope-analyzed. Syntax errors and incomplete edits may cause provisional or missing variable diagnostics.
Unused checks track references within this document, not runtime reachability or external evaluation. The checker does not validate module availability, signature ordering/arity, regex grammar, or version-specific syntax. These limits keep the extension independent of a local Perl runtime and avoid executing project code while typing.
Settings
| Setting |
Default |
Purpose |
perlLiveLinter.enabled |
true |
Enable or disable all diagnostics. |
perlLiveLinter.delay |
200 |
Debounce delay in milliseconds, from 0 to 2000. |
perlLiveLinter.rules.undeclared-variable |
error |
Undeclared variable severity. |
perlLiveLinter.rules.unused-variable |
warning |
Unused variable severity; underlined by default. |
perlLiveLinter.rules.shadowed-variable |
warning |
Shadowing severity. |
perlLiveLinter.rules.redeclared-variable |
warning |
Same-scope redeclaration severity. |
perlLiveLinter.rules.syntax-error |
error |
Syntax diagnostic severity. |
perlLiveLinter.rules.duplicate-hash-key |
warning |
Repeated literal keys in hash lists and hash references. |
perlLiveLinter.rules.hash-initialization |
error |
Hash initialization and assignment. |
perlLiveLinter.rules.array-initialization |
error |
Bare reference assigned to an array. |
perlLiveLinter.rules.scalar-list-assignment |
warning |
List expression assigned to a scalar. |
perlLiveLinter.rules.collection-access |
error |
Incorrect collection/reference access syntax. |
perlLiveLinter.rules.reference-type |
error |
Incompatible reference dereferencing. |
Each rule accepts off, error, warning, information, or hint. Configure these in VS Code Settings or workspace .vscode/settings.json; changes take effect immediately. For example:
{
"perlLiveLinter.rules.unused-variable": "warning",
"perlLiveLinter.rules.shadowed-variable": "off"
}
Development
Use Node.js 20 or newer:
npm test
npm run check
src/lexer.js tokenizes source and quoted expressions. src/analyzer.js tracks declarations, references, and scope and produces diagnostics with UTF-16 offsets. src/extension.js maps them to VS Code diagnostics, severity, and related declaration locations. It also registers navigation, rename, completion, outline, and quick-fix providers.
Tests cover the original rule, new syntax and variable rules, and editor lifecycle using a mocked VS Code API. They do not launch a real Extension Development Host.
References: VS Code diagnostics, Perl signatures and lexical scope, the vars pragma.