A CLI tool and library which migrates a PHP coding-standard configuration to another coding standard. Mago is the migration target, the supported sources are PHP-CS-Fixer, PHP_CodeSniffer, and Pint.
| Migration path | Source configuration |
|---|---|
php-cs-fixer → mago |
.php-cs-fixer.php, .php-cs-fixer.dist.php, .php_cs, .php_cs.dist |
phpcs → mago |
phpcs.xml, phpcs.xml.dist, .phpcs.xml, .phpcs.xml.dist, ruleset.xml |
pint → mago |
pint.json, .pint.json |
Since the coding standards do not overlap one to one, a migration is a starting point and not a finished configuration. Every migration therefore comes with a report which states what was translated, what the target standard does implicitly, and which rules need a manual decision.
composer require --dev stolt/coding-standard-migratorSee how much of the configuration of the current directory a migration would carry over, before running one:
vendor/bin/coding-standard-migrator analyze PHP-CS-Fixer configuration
──────────────────────────
Rules: 10
Mappable: 6
Equivalent: 5
Partial: 1
Unsupported: 2
No mapping known: 1
Disabled in source: 0
Linter candidates: 3
Redundant with Mago: 1
Migration confidence: 65%Mappable is the sum of Equivalent and Partial, the rules which end up in the
migrated configuration. The other three rule counts each name a reason why a rule does
not: Unsupported and No mapping known need a manual decision, Disabled in source
never asked for anything. Linter candidates and Redundant with Mago are cross
sections of the same rules, the ones which need a [linter.rules] entry, and the ones
the Mago formatter applies anyway.
The migration confidence is the share of the source configuration which survives, with
a partial mapping counting half and a rule the target standard covers anyway or which
was disabled to begin with counting fully. Add --verbose to list the rules which need
a manual decision, or --fail-under 80 to turn the confidence into a CI gate.
Migrate the PHP-CS-Fixer configuration of the current directory to a mago.toml:
vendor/bin/coding-standard-migrator migrateMigrate a PHP_CodeSniffer ruleset or a Pint configuration instead:
vendor/bin/coding-standard-migrator migrate --from phpcs
vendor/bin/coding-standard-migrator migrate --from pintPreview the migration without writing anything:
vendor/bin/coding-standard-migrator migrate --dry-runMapped rules
------------
------------------------ ---------------------- ----------------------------------
Rule Outcome Mago
------------------------ ---------------------- ----------------------------------
Config::setIndent() formatter option use-tabs = false, tab-width = 4
@PSR12 formatter option preset = "psr-12"
array_syntax linter rule array-style
no_trailing_whitespace covered by formatter -
header_comment unsupported -
------------------------ ---------------------- ---------------------------------- | Option | Description |
|---|---|
--from |
The coding standard to migrate from, defaults to php-cs-fixer |
--to |
The coding standard to migrate to, defaults to mago |
--working-dir, -d |
The directory to run in, defaults to the current one |
--config, -c |
The source configuration file, autodetected when omitted |
--target, -t |
The file to write to, defaults to the target standard's default |
--php-version |
The PHP version to pin in the migrated configuration |
--path, -p |
A path the coding standard applies to, repeatable |
--dry-run |
Shows the migrated configuration instead of writing it |
--force, -f |
Overwrites an existing target configuration |
--fail-on-unmapped |
Exits non-zero when a rule could not be migrated, made for CI |
| Option | Description |
|---|---|
--from |
The coding standard to analyze, defaults to php-cs-fixer |
--to |
The coding standard to analyze the migration to, defaults to mago |
--working-dir, -d |
The directory to run in, defaults to the current one |
--config, -c |
The configuration file to analyze, autodetected when omitted |
--fail-under |
Exits non-zero below this migration confidence, made for CI |
The command is also reachable as analyse. The supported standards and migration paths
are listed by the standards command.
use Stolt\CodingStandardMigrator\Migration\MigrationEngine;
use Stolt\CodingStandardMigrator\Migration\MigrationRequest;
use Stolt\CodingStandardMigrator\Standard\Standard;
$engine = new MigrationEngine();
$request = new MigrationRequest(
from: Standard::PhpCsFixer,
to: Standard::Mago,
sourceFile: $engine->locateSourceConfiguration(Standard::PhpCsFixer, \getcwd()),
phpVersion: '8.3',
);
echo $engine->analyze($request)->confidence() . '%' . \PHP_EOL;
$result = $engine->migrate($request);
foreach ($result->report as $mapping) {
echo $mapping->sourceRule . ': ' . $mapping->outcome->label() . \PHP_EOL;
}
if ($result->isComplete()) {
\file_put_contents($result->targetFileName, $result->configuration);
}ConfigurationReader -> Ruleset -> RuleMapper -> MappingReport -> ConfigurationWriter
-
A
ConfigurationReaderturns the configuration file of the source standard into a tool-agnosticSourceConfiguration, holding aRulesetplus the settings which live outside of the rules, such as the indent and the line ending. -
A
RuleMappertranslates everyRuleinto aMappingand collects them in aMappingReport. Each mapping carries one of these outcomes:Outcome Meaning linter ruleThe rule became one or more rules of the target linter formatter optionThe rule became one or more options of the target formatter covered by formatterThe target formatter does this anyway, no configuration needed skippedThe rule was disabled in the source and needed no translation unsupportedThe target standard knowingly has no equivalent unknownNo mapping is known, the rule needs a manual decision A translated mapping additionally carries a
MappingFidelity, eitherequivalentorpartial. Mapping tables state their caveats as notes, which makes a note the default signal for a partial mapping, overridable per mapping. -
A
ConfigurationWriterrenders the report as the configuration file of the target standard. Rules which need attention are added as comments to the generated file.MigrationAnalysisinstead reduces the report to the counts theanalyzecommand shows, which is why analyzing never touches a writer.
Mappings are only added to a
RuleTable once they are verified against the
Mago formatter reference
and the Mago linter rules.
Everything else stays unknown, so a migration never invents a setting which does not
exist.
Rule sets are shared ground between the standards: a @PSR12 rule of PHP-CS-Fixer, a
<rule ref="PSR12"/> of PHP_CodeSniffer, and a "preset": "psr12" of Pint all end up
as the same rule-set entry, which the tables map onto a Mago formatter preset.
There are two rule vocabularies and therefore two tables:
PhpCsFixerToMago\MappingTable, which is shared by the PHP-CS-Fixer and the Pint migration, as Pint is a wrapper around PHP-CS-Fixer and configures its rules;PhpCodeSnifferToMago\MappingTable, which maps sniffs by theirStandard.Category.Sniffname and ignores the error code of aStandard.Category.Sniff.ErrorCodereference.
Adding one means adding implementations, not changing the engine:
- as a migration source: implement
Reader\ConfigurationReaderplus aMapping\RuleMapperper target standard. For Mago as the target, extendMapping\Mago\AbstractRuleMapper, which brings the pipeline, and point it at aMapping\Mago\RuleTable; - as a migration target: implement
Writer\ConfigurationWriterplus aMapping\RuleMapperper source standard.
Register them in Standard\StandardRegistry and the migrate command picks up the new
migration path.
- Rule sets without a Mago preset, such as
@Symfonyor theSquizstandard, are not expanded into their rules. They are reported so they can be migrated rule by rule, for instance by way ofphp-cs-fixer describeorphpcs --explain. - For PHP-CS-Fixer and Pint the paths of the target configuration are guessed from the
directories next to the source configuration, as PHP-CS-Fixer keeps them in a
Symfony\Component\Finder\Finderinstance which does not expose them, and Pint defaults to the whole project. Pass--pathto be explicit. PHP_CodeSniffer rulesets declare their paths, so they are read from the<file>elements. - The mapping tables cover the commonly used rules and sniffs, not all of the several hundred PHP-CS-Fixer rules or the sniffs of every third party standard.
- Exclusions are not migrated yet, neither the
exclude,notName, andnotPathkeys of a Pint configuration nor the<exclude-pattern>elements of a PHP_CodeSniffer ruleset end up in Mago'ssource.excludes.
If you're considering contributing to this library, have a look at this repository's CONTRIBUTING.md for more advice.
This package is licensed under the MIT license, see LICENSE.md.
