Migrating to DressCode
Your suppression comments keep working, even with the old rule names, and one command converts your configuration and tells you what it could not carry over. Here are the common steps for migrating from any code style tool, such as PHP CS Fixer or PHP_CodeSniffer, and an honest list of what will be different.
Why It Doesn't Hurt
I know how it looks: you have a configuration with years of tuning in it, hundreds of phpcs:ignore comments in
the code, maybe a few custom sniffs, and on top of that a CI that runs it all. Nobody changes tools because the new one is
prettier. You switch when switching does not hurt, and that is exactly how DressCode is built.
The key thing: for each of its decisions, DressCode knows which rules of PHP CS Fixer, PHP_CodeSniffer and Slevomat it
covers. When you run dresscode catalogue, you see them next to every decision, in the parentheses that start with
covers. Everything else follows from this: the comments in your code work without changes, and the configuration can
be converted automatically. In the configuration itself and on the command line, however, you use only DressCode decision paths,
for example imports.unused.
The process has three steps, and you can undo each of them.
1. Leave the Code as It Is
The comments // phpcs:ignore, phpcs:disable, phpcs:enable, phpcs:ignoreFile
and the @phpcsSuppress annotation keep working. DressCode reads them, maps the other tool's rule name to the
decision that covers it, and the suppression applies. So you do not have to touch the code at all yet, and you can run the first
check right away:
dresscode check src tests --use perCs
There is one difference you need to know: in PHP_CodeSniffer, phpcs:ignore on a line of its own suppressed only
the next line. DressCode reads it the same way as its own dresscode:ignore, and that one, on a line of its own,
applies to the whole next statement, even if it takes up several lines. So a suppression may cover more than it did before.
You have no configuration yet, and without one DressCode checks nothing, so the --use option sets the standard.
perCs means PER Coding Style 3.1. If you used
@PER-CS or PSR12 until now, you will see roughly what you expected. If you had tuned things beyond the
standard, you will see differences, which the next step resolves.
2. Translate the Configuration
The import command reads the configuration of another tool and writes its DressCode equivalent to the file you
name after it (if you come from a tool without a configuration, or have none, let DressCode measure one from your code with dresscode init):
dresscode import phpcs.xml dresscode.neon
dresscode import .php-cs-fixer.dist.php dresscode.neon
The format follows the extension, dresscode.neon or dresscode.php, and import does not
overwrite a file that already exists. Without a target, it prints the configuration as PHP to standard output.
phpcs.xml is XML, so it can be read with nothing else installed. In contrast, .php-cs-fixer.dist.php
is PHP that has to be executed, and it returns an object of another library. import reads it only when it runs as
vendor/bin/dresscode in a project where PHP CS Fixer is still installed; DressCode installed globally or with
create-project cannot find its classes and stops with a configuration error (details on the page PHP CS Fixer). So translate the configuration before
you remove the old tool.
A phpcs.xml that builds on PSR12, limits the line length to 100 characters, checks for unused
imports and requires function documentation gives this dresscode.neon:
use: [psr12]
file:
longLines: forbidden
lineLengthExcept: [imports]
lineLength: 100
imports:
unused: forbidden
Below the message saying where it wrote the file, import prints a summary and what it could not carry over:
Read 4 rules; set 4 decisions and 1 preset. `SlevomatCodingStandard.Namespaces.UnusedUses` without `searchAnnotations` removes an import only a doc comment uses; DressCode keeps it. No DressCode rule covers `Squiz.Commenting.FunctionComment`.
These lines are as valuable as the configuration itself: they tell you where you have to decide. Here, for example, that
DressCode keeps an import used only by a doc comment, while the sniff without searchAnnotations removed it. A rule
with no counterpart is either one you do not need, or one you write yourself.
Sets like @PSR12 or PSR12 are translated to standards; for sets with no counterpart (for example
@PhpCsFixer), import reports this and recommends starting from the perCs or
psr12 standard.
One rule of another tool often splits into several decisions, values included: Generic.Files.LineLength with
absoluteLineLimit gave file.longLines, file.lineLength and
file.lineLengthExcept. Not every option of the other tool has a counterpart, though, and import lists
those too. A rule that the old configuration turned off gets the value keep here as well. But when the same decision
is also covered by another rule of the same tool, the command leaves the decision alone and prints a warning, because it cannot
know whether one of those rules still applies. Go through the resulting file; it is short.
3. Rewrite the Comments
The comments in the code work with the old names too, but decision paths are shorter and easier to read. One command does the rewrite:
dresscode migrate-suppressions src tests
phpcs:ignore SlevomatCodingStandard.Namespaces.UnusedUses becomes dresscode:ignore imports.unused,
phpcs:disable and phpcs:enable become dresscode:disable and dresscode:enable,
and phpcs:ignoreFile becomes dresscode:ignoreFile. The @phpcsSuppress annotation stays an
annotation, only the name in it changes. When the command rewrites a phpcs:ignore on a line of its own, it reminds
you of the difference in scope with a note, and the rewritten places are worth
reviewing. A name that no decision covers stays as it was, and the command lists it:
Migrated 5 suppression comments in 1 file. Warning: The names DressCode does not know are kept as they are: `Acme.Custom.Sniff`. Note: A `dresscode:ignore` on a line of its own covers the whole statement below it, not just the next line; review the migrated ones.
From this output you get a list of suppressions that suppress nothing, because DressCode does not know a rule with that name.
The First Fix
Once the configuration fits, run dresscode fix and commit the result separately, with no other changes. Even for a
rule that is “the same”, the fix may differ by a space from what the old tool did, and going through it line by line makes no
sense. What makes sense is knowing that the commit contains nothing else. In a large project, where one commit would be hard to
follow, fix by sets or by sections, dresscode fix --only cleanup or dresscode fix --only blankLines, and
commit each step separately; a final full fix then changes the rest, see Cleaning Up Old Code Step by Step. What
fix cannot fix stays in the output, and when there is more of it than you want to deal with right now, the
dresscode baseline command records it in a baseline,
and only new violations will be reported.
What Will Be Different
A guide that promises a seamless migration loses your trust at the first difference, so here they are up front:
- Decisions instead of rules. The configuration does not list rules and their options, it says what the code should look
like:
blankLines.aroundMethods: 1orimports.unused: forbidden. Which rules take care of it is DressCode's business, see Decisions Instead of Rules. The Configuration Reference shows all decisions in one place. - There are no priorities. If your results relied on the order of fixers, you will get a different shape of code in some files. Here the rules run repeatedly until the result settles; why.
- Rules with no counterpart are listed by both
importandmigrate-suppressions. There are quite a few of them, mostly among the sniffs about documentation and among the rules where the tools overlap with each other. - A risky rule does not have to be risky here. What PHP CS Fixer turned on only when you explicitly asked, because on
tokens it could not tell a safe case from a dangerous one, is decided here by the tree, and only the safe occurrences are fixed.
A fix that can change behavior is reported as a risky fix
and is made only for a decision listed in
fixRiskyin the configuration, or with--fix-risky. And a decision that changes the behavior of the program by its nature (expressions.comparison.loose: forbiddenturns==into===) stays your decision, wherever you write it. - The exit codes are different:
0clean,1violations,2a failure on some file,3an error on the command line or in the configuration. PHP_CodeSniffer and PHP CS Fixer each have their own scale, so a script that evaluates them needs one change. - One tool instead of two. If you combined CodeSniffer and Fixer, you had two configurations and two steps in CI. Now there is one of each.
- You can try a decision without touching the configuration.
dresscode check --set controlFlow.elseAfterReturn=forbidden --only controlFlow.elseAfterReturn --diffshows what the decision would do with your code, anddresscode fix --set …adds it to the rest of the configuration, so its changes come out already formatted by your standard, see Command Line. - Instead of listing rules, you state your intent. The configuration rests on two questions: the standard says what the
code should look like, and the sets say what you want
from it besides its looks. There are six sets, and their names are a closed list, so the
cleanupentry in theusekey cannot turn into twenty lines of rule names, not even when the catalogue grows. - Upgrading code is in the same tool. DressCode also rewrites code for a newer PHP and for the new API of your libraries, and in the same run it formats the result according to the standard you have just converted. So you do not need another tool with its own configuration to upgrade to a new version, see Upgrading to a Newer PHP and Upgrading Code.
Details for a specific tool: PHP CS Fixer, PHP_CodeSniffer and Slevomat, Nette Coding Standard.