This is a very early work-in-progress linter for Laravel projects. Itβs different from other PHP linters in that it focuses on building custom rules for your specific needs.
The goal is to make writing custom linters easy and fluent, just like writing Laravel-backed code. While the project uses an AST/tokenizers/etc under-the-hood, most common use-cases shouldnβt need a deep understanding of how that works.
Install this into your project with:
composer require glhd/laralint --devOnce installed, you can lint your project using LaraLintβs default rules by running:
php artisan laralint:lintIf you only want to lint uncommitted Git changes, you can pass the --diff
flat to the command
php artisan laralint:lint --diffOr, ff you only want to lint specific files, pass their filenames as the first argument:
php artisan laralint:lint app/Http/Controllers/HomeController.phpLaraLint comes with a very opinionated preset installed. If this works for you, great, but youβll probably want to customize things to match your teamβs code standards.
To start, publish the LaraLint config file:
php artisan vendor:publish --tag=laralint-configThis will install a laralint.php file into your projectβs config/
directory with the default LaraLint config.
In some cases, you may be able to simply tweak the config to meet your needs. But itβs more likely that youβll want to create your own preset.
Take a look at the Preset contract, or the default LaraLint Preset to get started. From there you can create a custom preset for your project that uses linters that make sense for your team. Mix and match the pre-built linters with your own custom linters to your heartβs content!
If youβre interested in LaraLint, youβre probably interested in custom linters. Each teamβs needs are different, and LaraLint tries to make it as easy as possible to quickly add logic that enforces your agreed upon conventions.
That said, LaraLint does rely on concepts like Abstract Syntax Trees, which can be intimidating at first. But with a few key concepts under your belt, you should be traversing that tree like a champ in no time!
In as few words as possible:
LaraLint uses Microsoftβs Tolerant PHP Parser to parse your PHP code into an Abstract Syntax Tree, or AST. We use Microsoftβs parser because itβs fast and works well with partially-written code (like code youβre typing in your IDE). Itβs also the basis for VS Codeβs IntelliSense, so itβs got a good track record.
Think of this tree as a structured view of your code. Given the following PHP code:
class Foo
{
public function bar()
{
return 'baz';
}
}The AST will look something like this (simplified for clarityβs sake):
- ClassDeclaration (
class Foo)- MethodDeclaration (
public function bar())- CompoundStatementNode (
return 'baz';)- ReturnStatement (
return)- StringLiteral (
'baz')
- StringLiteral (
- ReturnStatement (
- CompoundStatementNode (
- MethodDeclaration (
LaraLint walks that tree from top to bottom, and passes each node to each applicable Linter for inspection. Linters are simply objects that receive AST nodes and optionally return linting results once the tree is fully walked.
This means you can write incredibly complex and custom linters, but hopefully you won't have to.
The core of most LaraLint Linters is a Matcher object. These objects
are designed to easily match a general AST βshapeβ and flag part of the
code if the entire βshapeβ is found. for example, if you wanted to flag
any method named bar that returns the string 'baz', you could use
the following matcher:
(new TreeMatcher())
// (1) Find a method declaration where the method name is "bar"
->withChild(function(MethodDeclaration $node) {
return 'bar' === $node->getName();
})
// (2) Find any return statement
->withChild(ReturnStatement::class)
// (3) Find a string literal that matches the string "baz"
->withChild(function(StringLiteral $node) {
return 'baz' === $node->getStringContentsText();
})
->onMatch(function(Collection $all_matched_nodes) {
// Create a linting result using the matched nodes.
// LaraLint will automatically map the AST nodes to line
// numbers when printing the results.
});As soon as LaraLint finds a node that matches the first rule, it will
begin looking for a child that matches the second rule. If it finds a
child that matches the second rule, it will move on to the third rule.
When it's matched all the rules, the matcher will trigger the onMatch
callback, where you can perform any additional logic you choose.
LaraLint comes with several βstrategiesβ that apply to common use-cases. These strategies abstract away even more of the logic, and are the best place to start. Look at some of the existing linters to understand how best to use each strategy.
OK, youβre probably looking at ReturnStatement and StringLiteral and
thinking, βI donβt speak Abstract Syntax Tree.β
Neither does anyone else.
That's where the laralint:dump command comes into play. Say youβre
trying to write the silly bar/baz linter from the example above. Simply
create a PHP file that should fail, and dump its tree:
php artisan laralint:dump barbaz_source.phpWhich will output something like:
βββ ClassDeclaration βββββββ
β β
β class Foo β
β { β
β public function bar() β
β { β
β return 'baz'; β
β } β
β } β
β β
β βββ ClassMembersNode βββββββ
β β β
β β { β
β β public function bar() β
β β { β
β β return 'baz'; β
β β } β
β β } β
β β β
β β βββ MethodDeclaration ββββ
β β β β
β β β public function bar() β
β β β { β
β β β return 'baz'; β
β β β } β
β β β β
β β β βββ CompoundStatementNode βββ
β β β β β
β β β β { β
β β β β return 'baz'; β
β β β β } β
β β β β β
β β β β βββ ReturnStatement βββ
β β β β β β
β β β β β return 'baz'; β
β β β β β β
β β β β β βββ StringLiteral βββ
β β β β β β β
β β β β β β 'baz' β
β β β β β β β
β β β β β βββββββββββββββββββββ
β β β β βββββββββββββββββββββββ
β β β βββββββββββββββββββββββββββββ
β β βββββββββββββββββββββββββββ
β βββββββββββββββββββββββββββββ
βββββββββββββββββββββββββββββ
Between the output of the dump command, and the example of existing Linters,
youβd be surprised how easy it is to start writing your own rules.
The best place for linting results is right in your IDE. Rather than publishing
our own IDE plugins, LaraLint can simply pretend to be PHP_CodeSniffer:
php artisan laralint:lint --printer=phpcsThis will give you XML output that is compatible with any plugin that can
parse PHP_CodeSnifferβs XML output (such as PhpStorm).
Running php artisan laralint:install will install a nice helper file that
makes this a little easier. Just configure your IDE to point to that
file, and your LaraLint lints should get flagged in your IDE.