Token-based rules
Token types
In order to write a custom rule, you first need to understand how the twig file is parsed.
The TwigCsFixer\Token\Tokenizer transform the file into a list of tokens which can be:
-
TwigCsFixer\Token\Token::EOF_TYPE
This token is the last one of the file.
-
TwigCsFixer\Token\Token::TEXT_TYPE
Any basic text which are not inside
{#,{{,{%delimiters. Does not include whitespaces. -
TwigCsFixer\Token\Token::BLOCK_START_TYPE
The
{%delimiter. -
TwigCsFixer\Token\Token::VAR_START_TYPE
The
{{delimiter. -
TwigCsFixer\Token\Token::BLOCK_END_TYPE
The
%}delimiter. -
TwigCsFixer\Token\Token::VAR_END_TYPE
The
}}delimiter. -
TwigCsFixer\Token\Token::NAME_TYPE
Any variable inside
{%or{{delimiters. Like name in{{ name }}or{% if foo(name) %} -
TwigCsFixer\Token\Token::NUMBER_TYPE
Any number inside
{%or{{delimiters. Like 42 in{{ 42 }}or{% if foo(42) %} -
TwigCsFixer\Token\Token::STRING_TYPE
Any single quote string or double quote string without interpolation string inside
{%or{{delimiters. Like'string'/"string"in{{ 'string' }},{% if foo('string') %},{{ "string" }}or{% if foo("string") %}. It can also include part of string with interpolation, like both string in{{ 'string#{interpolation}string' }}. -
TwigCsFixer\Token\Token::OPERATOR_TYPE
Any twig operator like
+,-,and,or, etc. Also include?and:when used in ternary. -
TwigCsFixer\Token\Token::PUNCTUATION_TYPE
One of the
(,),[,],{,},:,.,,,|characters. For:, only when it's not a ternary. -
TwigCsFixer\Token\Token::INTERPOLATION_START_TYPE
The characters
#{inside a double-quoted string. Like{{ "string #{interpolation}" }}. -
TwigCsFixer\Token\Token::INTERPOLATION_END_TYPE
The characters
}inside a double-quoted string. Like{{ "string #{interpolation}" }}. -
TwigCsFixer\Token\Token::ARROW_TYPE
The
=>used for arrow functions. -
TwigCsFixer\Token\Token::SPREAD_TYPE
The
...spread operator. -
TwigCsFixer\Token\Token::DQ_STRING_START_TYPE
The
"used at the start of double-quoted string with interpolation. Like{{ "string#{interpolation}" }}. -
TwigCsFixer\Token\Token::DQ_STRING_END_TYPE
The
"used at the end of double-quoted string with interpolation. Like{{ "string#{interpolation}" }}. -
TwigCsFixer\Token\Token::BLOCK_NAME_TYPE
The first non-empty element after the
{%token. Like if in{% if ... %}or block in{% block ... %}. -
TwigCsFixer\Token\Token::FUNCTION_NAME_TYPE
The name of a function. Like in
{{ function(foo) }}. -
TwigCsFixer\Token\Token::FILTER_NAME_TYPE
The name of a filter function. Like in
{{ foo|filter(bar) }}. -
TwigCsFixer\Token\Token::MACRO_NAME_TYPE
The name used in the definition of a macro function. Like
fooin{% macro foo() %}. -
TwigCsFixer\Token\Token::MACRO_VAR_NAME_TYPE
The name used for params of a macro function. Like
barin{% macro foo(bar) %}. -
TwigCsFixer\Token\Token::TEST_NAME_TYPE
The name of a test function. Like in
{% if foo is test(bar) %}. -
TwigCsFixer\Token\Token::HASH_KEY_NAME_TYPE
Any hash key represented as name. Like
fooin{ foo: bar }or{ foo }but not in{ 'foo': bar }. -
TwigCsFixer\Token\Token::TYPE_NAME_TYPE
Similar to hash key, but in types definition. Like
fooin{% types { foo: 'bar' } %}. -
TwigCsFixer\Token\Token::WHITESPACE_TYPE
Any whitespace separating text or expressions. Does not include commented whitespaces.
-
TwigCsFixer\Token\Token::TAB_TYPE
Any tabulation separating text or expressions. Does not include commented tabulations.
-
TwigCsFixer\Token\Token::EOL_TYPE
Any end of line except commented end of lines.
-
TwigCsFixer\Token\Token::COMMENT_START_TYPE
The
{#delimiter. -
TwigCsFixer\Token\Token::COMMENT_TEXT_TYPE
Any commented text. Does not include whitespaces.
-
TwigCsFixer\Token\Token::COMMENT_WHITESPACE_TYPE
Any commented whitespace.
-
TwigCsFixer\Token\Token::COMMENT_TAB_TYPE
Any commented tabulation.
-
TwigCsFixer\Token\Token::COMMENT_EOL_TYPE
Any commented end of line.
-
TwigCsFixer\Token\Token::COMMENT_END_TYPE
The
#}delimiter. -
TwigCsFixer\Token\Token::INLINE_COMMENT_START_TYPE
The
#delimiter. -
TwigCsFixer\Token\Token::INLINE_COMMENT_TEXT_TYPE
Any commented text inside an inline comment. Does not include whitespaces.
-
TwigCsFixer\Token\Token::INLINE_COMMENT_WHITESPACE_TYPE
Any commented whitespace inside an inline comment.
-
TwigCsFixer\Token\Token::INLINE_COMMENT_TAB_TYPE
Any commented tabulation inside an inline comment.
-
TwigCsFixer\Token\Token::NAMED_ARGUMENT_SEPARATOR_TYPE
The
=or:separator used when using named argument. Like{{ foo(bar=true, baz: false) }}.
Rule
Then, the easiest way to write a custom rule is to implement the TwigCsFixer\Rules\AbstractRule class
or the TwigCsFixer\Rules\AbstractFixableRule if the rule can be automatically fixed.
final class MyCustomRule extends \TwigCsFixer\Rules\AbstractRule {
protected function process(int $tokenIndex, \TwigCsFixer\Token\Tokens $tokens): void
{
$token = $tokens->get($tokenIndex);
if (!$token->isMatching(...)) {
// Skip if the token is not matching some conditions.
return;
}
$nextToken = $tokens->findNext(...); // Look for next token based on conditions.
$previousToken = $tokens->findPrevious(...); // Look for previous token based on conditions.
if (...) {
// Skip if the token is valid.
return;
}
// Use $this->addError(...) if the error is not fixable.
$fixer = $this->addFixableError('Custom message.', $token);
if (null === $fixer) {
// Fixer is null when you're linting the file.
return;
}
$fixer->beginChangeSet();
// Use some of the following functions base of the wanted behavior:
// $fixer->replaceToken(...);
// $fixer->addNewline(...);
// $fixer->addNewlineBefore(...);
// $fixer->addContent(...);
// $fixer->addContentBefore(...);
$fixer->endChangeSet();
}
}
Node-based rules
Rules can also be based on the Twig Node and NodeVisitor logic. Because they are different from the default token based rules, these rules have some limitations:
- they cannot be fixable.
- they can only report the line with the error but not the token position.
Still, these rules can be easier to be written for some static analysis.
You can get inspiration from the src/Rules/Node folder.
final class MyCustomRule extends \TwigCsFixer\Rules\Node\AbstractNodeRule {
public function enterNode(\Twig\Node\Node $node, \Twig\Environment $env): Node
{
// Do some logic
}
}