php-regex / regex-linter
Lints the regex patterns of a PHP code base: extraction from PHP sources, lint rules, ReDoS and validity checks, reports in console, JSON, GitHub, Checkstyle and JUnit formats.
Fund package maintenance!
Requires
- php: >=8.2
- php-regex/regex-automata: ^2.0
- php-regex/regex-explain: ^2.0
- php-regex/regex-optimizer: ^2.0
- php-regex/regex-parser: ^2.0
- php-regex/regex-redos: ^2.0
Requires (Dev)
None
Suggests
- nikic/php-parser: To extract patterns with a full PHP parser instead of the tokenizer.
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-05 09:21:14 UTC
README
PHPRegex Linter
Lints the regex patterns of a PHP codebase: extraction from PHP sources, lint rules, ReDoS and validity checks, reports in console, JSON, GitHub, Checkstyle and JUnit formats.
Features
- 32 lint rules over the parsed AST: redundant groups and character classes, useless flags and quantifiers, lazy quantifiers that end the pattern, suspicious escapes and ranges, undefined backreferences, impossible anchors, multibyte text PCRE reads as bytes without
/u. - Patterns are validated before they are linted: parse and semantic errors arrive with a position and a tip, not a guess.
- Extraction from PHP sources: native
preg_*calls, the composer/pcre, nette/utils, spatie/regex and LaravelStrwrappers, and your own helper functions — with@regex-ignorecomments to suppress a finding inline. - ReDoS detection through php-regex/regex-redos: proven verdicts with their attack input, theoretical or confirmed mode, four severity thresholds.
- Optimization suggestions through php-regex/regex-optimizer, each rewrite checked for equivalence by the automata solver.
- Five report formats: console, JSON, GitHub annotations, Checkstyle, JUnit.
Installation
composer require php-regex/regex-linter
Requires PHP 8.2+. The extractor runs on the PHP tokenizer by default; install nikic/php-parser to extract from a full PHP parser instead.
To lint a whole codebase from the terminal, add the console package:
composer require --dev php-regex/regex-cli vendor/bin/regex lint src/
Configuration
The lint command reads a regex.json (committed) or regex.dist.json (template) from the project root. This package ships the JSON Schema as regex.schema.json, so editors validate the file as you type.
| Key | Default | What it does |
|---|---|---|
paths, exclude |
working directory, vendor |
Directories or files to scan, and paths to skip |
phpVersion, pcreVersion |
from composer.json | The PHP ("8.3", 80300, "runtime") and PCRE2 ("10.44") releases the patterns are judged for |
jobs |
CPU cores | Parallel workers |
format |
console |
console, json, github, checkstyle or junit |
ide |
none | phpstorm, vscode, others, or a URL template — for clickable links |
extraction.interop |
["composer-pcre"] |
Wrapper presets to read: composer-pcre, nette-utils, spatie-regex, laravel-str |
extraction.functions |
[] |
Project helpers carrying a pattern, as App\Support\Str::matches#1 |
checks.validation |
true |
Syntax and semantic validation |
checks.redos.enabled |
false |
ReDoS analysis |
checks.redos.mode, checks.redos.threshold |
theoretical, high |
Analysis mode, and the lowest severity reported |
checks.optimizations.enabled, checks.optimizations.minSavings |
true, 1 |
Optimization suggestions, and the minimum characters saved to report one |
checks.lint.enabled |
true |
Lint rules |
checks.lint.rules.<id> |
true |
One boolean per rule id; unicode.shorthandWithoutU defaults to false |
Usage
PatternLinter is a node visitor: run it on any parsed AST.
use PHPRegex\Linter\PatternLinter; use PHPRegex\Parser\RegexParser; $linter = new PatternLinter(); RegexParser::create()->parse('/(a+)+b/')->accept($linter); foreach ($linter->getIssues() as $violation) { echo $violation->id, ': ', $violation->message, "\n"; }
regex.lint.quantifier.nested: Nested quantifiers can cause catastrophic backtracking.
regex.lint.group.quantifiedCapture: Quantified capturing group "(...)" with "+": only the last iteration's capture is retained.
Turn rules on or off by id — the same map checks.lint.rules reads from regex.json:
use PHPRegex\Linter\PatternLinter; use PHPRegex\Parser\RegexParser; $linter = new PatternLinter([ 'quantifier.nested' => false, // disable one rule 'unicode.shorthandWithoutU' => true, // enable the opt-in rule ]); RegexParser::create()->parse('/^\w+\d*$/')->accept($linter); foreach ($linter->getIssues() as $violation) { echo $violation->id, ': ', $violation->message, "\n"; }
regex.lint.quantifier.concatenation: Concatenated quantifiers can be optimized when one character set is a subset of the other.
regex.lint.unicode.shorthandWithoutU: Shorthand "\w" matches only ASCII without /u flag.
regex.lint.unicode.shorthandWithoutU: Shorthand "\d" matches only ASCII without /u flag.
A whole codebase, from the terminal — exits non-zero when a pattern does not compile, a confirmed ReDoS verdict reaches high, or a rule of error severity fires (the /u rules: a multibyte character in a class, a quantified multibyte character, a Unicode property); --format=github fits CI:
vendor/bin/regex lint src/
demo.php:4:30
→ /(a+)+b/
WARN Nested quantifiers can cause catastrophic backtracking.
↳ Consider atomic groups (?>...) or possessive quantifiers — verify the rewrite still matches everything you need.
Documentation
- Quick start — the lint command among the first five to know
- CLI guide — every
regex lintoption and the fullregex.jsonreference - Diagnostics — how issues are reported and how to read them
- ReDoS guide — risky shapes, detection modes and mitigations
This package is part of PHPRegex, released with its siblings under one version number; read the backward compatibility promise.
Resources
- Documentation
- The console that drives this linter: regex-cli
- Changelog
- Report issues and send pull requests in the main PHPRegex repository
Sponsors
If PHPRegex saves you time, consider sponsoring its maintenance.
License
MIT. See LICENSE.