Skip to content

Coming from Coder

These pages list every sniff run by Coder 9 in its Drupal and DrupalPractice standards, and what takes its place in a Mago setup with this extension and its configuration. To find a check, search for the sniff or the code from a phpcs report or a phpcs:ignore comment. phpcs names a check Standard.Category.Sniff.Code.

The Handled by column names one or more of:

  • a rule of this extension, such as drupal/doc-comment. Its entry under Linter rules lists the codes ported by the rule, what its fix does, and where it differs from Coder;
  • a rule built into Mago, such as Mago no-empty-comment;
  • mago format, the formatter with the drupal preset;
  • mago analyze, the analyzer;
  • phpcs, for a check of a Drupal 7 .info file or of a .txt or .md file, which Mago doesn't read;
  • nothing, when no tool checks it yet. The notes say why.

A sniff whose name is a link has a section below the table. The section lists the codes for each place when they go to different places, and has the notes on where the setup differs from Coder. Codes excluded by Coder's rulesets aren't listed, and neither are codes never reported by Coder.

What replaces phpcs

Part of the standard Checked by
Whitespace, indentation, braces, docblock star alignment mago format
Class, interface and file naming Mago's naming rules
Function aliases, unused imports no-alias-function, no-redundant-use, enabled by default
Unreachable code, deprecated PHP functions, wrong @param and @return types mago analyze
Everything specific to Drupal This extension

drupal.mago.toml makes mago lint fail on a warning, as phpcs does, and gives Mago's own rules in this table the level used by Coder, see What fails a run.

The checks of .info.yml and .routing.yml files need yml in Mago's extensions, as Install shows. The Drupal.InfoFiles sniffs that aren't ported check only the .info files of Drupal 7.

One check still needs phpcs: Drupal.Files.TxtFileLineLength, which checks the line length of .txt and .md files. Mago reads every file as PHP, and a <?php example in such a file gives parse errors.

DrupalPractice.Objects.GlobalDrupal reports a \Drupal::service() call where injection is possible. The analyzer check drupal/global-drupal-call ports it, and reads the services file of every module, where Coder reads only the nearest, shortest-named one. DrupalPractice.Objects.GlobalClass reports a static Node::load() call in the same classes, and drupal/static-entity-load ports it for every entity class.

To keep the line length check of text files, run phpcs with that sniff on those files only:

vendor/bin/phpcs --standard=Drupal --sniffs=Drupal.Files.TxtFileLineLength --extensions=txt,md web/modules/custom

A Drupal 7 project also needs phpcs for its .info files, which aren't read by the rules:

vendor/bin/phpcs --standard=Drupal --sniffs=Drupal.InfoFiles.ClassFiles,Drupal.InfoFiles.DuplicateEntry,Drupal.InfoFiles.Required,Drupal.InfoFiles.AutoAddedKeys --extensions=info sites/all/modules/custom

Differences that apply to every rule

Suppression comments. With the --phpcs-ignores worker argument, a phpcs suppression comment (phpcs:ignore, phpcs:disable, phpcs:ignoreFile and the older @codingStandards forms) drops the issues of the rules and checks that port the codes it ignores. See phpcs ignores. It's off by default, and drupal-core.mago.toml turns it on. The comments don't reach Mago's own rules, so a line that phpcs ignores for one of those needs a // @mago-expect lint:<code> or a Mago exclude. In core, phpcs:ignoreFile marks the copied Doctrine annotation classes, test fixtures, the generated ProxyClass files and a few other files, and drupal-core.mago.toml leaves them out of the linter.

Core's configuration. Core's phpcs.xml.dist turns off some checks enabled by the Drupal standard, and doesn't run some sniffs at all:

  • it turns off InvalidEndChar and SpacingAfter of Drupal.Commenting.InlineComment, LongFullStop of Drupal.Commenting.DocComment, PSR2.Methods.MethodDeclaration.Underscore and PSR2.Classes.PropertyDeclaration.Underscore;
  • it runs MissingShort of Drupal.Commenting.DocComment and Missing of Drupal.Commenting.FunctionComment only in some directories, and Drupal.Files.LineLength everywhere except in tests directories;
  • it enables only WrongOpenercase of PSR2.ControlStructures.SwitchDeclaration;
  • it doesn't run Drupal.NamingConventions.ValidFunctionName, Drupal.Semantics.ConstantName.ConstConstantStart, Drupal.Attributes.ValidHookName, Drupal.InfoFiles.DependenciesArray, Drupal.Semantics.UnsilencedDeprecation, Generic.CodeAnalysis.UselessOverridingMethod, Generic.NamingConventions.TraitNameSuffix or SlevomatCodingStandard.PHP.ShortList;
  • it runs only ExpectedException, ExceptionT, GlobalFunction, NamespacedDependency and part of VariableAnalysis from the DrupalPractice standard.

The rules that port those checks run on contrib and custom code. For Drupal core, drupal-core.mago.toml turns them off, or they run only in the directories where core runs them. It also turns off the parts of Mago's own rules that match those sniffs, such as the Trait suffix check of trait-name, which drupal.mago.toml turns on. A project that turns other sub-codes off in its phpcs config can turn off the matching rules under [linter.rules]. An <exclude-pattern> inside a <rule> of the phpcs config becomes an exclude of the matching rule, such as "drupal/missing-function-comment" = { exclude = ["tests/"] }.

File types. Mago reads only the file extensions in its [source] block. phpcs reads the extensions in its --extensions option, php and inc when none is given. With the usual Coder options, it also checks .test and .info files, two Drupal 7 formats, and .txt and .md files. When phpcs scans a directory, it skips a file whose name starts with a dot, such as .phpstan-baseline.php. Mago reads it. drupal.mago.toml leaves .ht.router.php, which every site has, out of the linter, but a Mago glob can't match every dot-file without also matching each file of a project that sits inside a dot-directory.

Comment whitespace and the formatter

The rules port Drupal.Commenting.*, whitespace included, with a fix wherever phpcbf has one, except for a missing docblock: phpcbf inserts an empty one, and the rules only report it. mago format covers some of the whitespace instead:

  • It produces the result of DocCommentStar (a star on a docblock line that has none) and the star column of DocCommentAlignment, so those aren't ported. It doesn't touch the text after a star, so drupal/doc-comment ports the space after the star.
  • It turns several blank lines into one, so drupal/file-comment reports only a file docblock with no blank line below it, and leaves SpacingAfterComment for more than one to the formatter.
  • It sets the blank line between a comment and a closing bracket. It takes the line out, or moves it above the comment when a statement comes before the comment, except before the closing brace of a class, interface, trait or enum with members, where it always writes one, after a comment too. Coder reports that line as SpacingAfter, and a fix that removes it would undo the formatter on every run. drupal/inline-comment-blank-line leaves all of these blank lines to the formatter, SpacingAfterAtFunctionEnd included, and reports the rest of SpacingAfter.

The checks read a docblock the way Coder does. A tag is a line that starts with @. Two kinds of @ lines stay text. An @ line below another tag, indented like that tag's description, stays part of the description. An indented @code, @endcode, @link or @endlink stays part of the text around it. Coder reads both as tags. Only the tags at the column of the first one form the groups looked at by the blank-line checks.

Docblock types and the analyzer

Drupal.Commenting.FunctionComment compares a docblock against a function's real parameter list and return type. Most of those checks are redundant with mago analyze. The analyzer reads @param, @return, @var and @throws as authoritative types when there is no native hint, the same way PHPStan and Psalm do. It already reports an @return void on a function that returns a value, a function with no return at all, an @param that names an unknown parameter, and a type name that doesn't resolve as a class, such as Boolean. It reads integer and boolean as int and bool without a report, so the rules check Coder's type names themselves.