Skip to content

Configuration

The package has two config files to extend from mago.toml, see Install:

  • drupal.mago.toml makes Mago match Drupal's standard, for a site, a module or a theme.
  • drupal-core.mago.toml adds what core's phpcs.xml.dist checks, for a core checkout.

Each setting in the files has a comment that says why it's there.

What drupal.mago.toml sets

  • Drupal's PHP file extensions, and yml for the rules for info and routing files. Other YAML files aren't read, and the formatter and the analyzer stay out of the info and routing files.
  • The worker, with Xdebug and pcov off, see Xdebug and pcov.
  • Mago's drupal formatter preset, and its drupal linter integration, which accepts TRUE, FALSE and NULL.
  • The Interface and Trait suffixes, and the file-name rule, as Coder 9 checks them.
  • minimum-fail-level = "warning" for the linter, so that a warning fails the run as it does in phpcs, see What fails a run.
  • Coder's level for the Mago rules that stand in for a sniff, such as class-name and array-style. Mago reports them as help or as a note, which doesn't fail a run.
  • check-name-casing for the analyzer, which ports Squiz.PHP.LowercasePHPFunctions. The analyzer stays out of *.api.php files and test fixtures, as phpstan-drupal does.
  • Mago's rules that contradict the standard or repeat a rule of this extension, turned off, such as tagged-todo and function-name. The same goes for advice that the standard doesn't ask for, such as no-else-clause and strict-types. Turning them off doesn't lose any check done by phpcs.
  • Excludes for the paths where a Mago rule reports what Drupal does on purpose, such as the fake credentials in tests. The linter also skips .ht.router.php, as phpcs skips a file whose name starts with a dot.

It doesn't set php-version, since that's up to the project. Without it, Mago assumes its newest PHP version, and the formatter writes syntax that older versions reject, such as new Foo()->bar().

Changing a setting

The project's mago.toml wins over the file that it extends. A table is merged into the one in the file, so one line changes one rule. An array is added to the one in the file. The worker command is replaced as a whole.

extends = "vendor/drupal/mago/drupal.mago.toml"
php-version = "8.3"

[source]
# Coder also checks .test files, a Drupal 7 format.
extensions = ["test"]

[linter.rules]
strict-types = { enabled = true }

[analyzer]
# Let @phpstan-ignore comments drop the Mago issues that report the same finding.
plugins = ["phpstan-ignores"]

See PHPStan ignores for the plugin.

Turning rules off

Mago takes this extension's rule codes under [linter.rules], with the same enabled, level and exclude settings as its own rules. Quote the code, because it holds a /:

[linter.rules]
"drupal/inline-comment-punctuation" = { enabled = false }
"drupal/missing-function-comment" = { exclude = ["tests/"] }
"drupal/else-if" = { level = "warning" }

enabled = false turns a rule off. exclude leaves the rule out of the matching paths, which take the same globs and directory prefixes as the excludes of Mago's own rules. level sets the level of every issue that the rule reports.

A rule that is turned off still runs when mago lint --only names it, and mago lint --pedantic doesn't read these settings. Mago warns about a code that doesn't name a rule, and the run goes on.

A project that turns phpcs sub-codes off in its phpcs.xml can turn off the matching rules this way. Coming from Coder says which rule ports each sub-code.

[linter.rules] takes only linter rules. To turn off an analyzer check, use Mago's own [analyzer] ignore, for the whole project or for some paths:

[analyzer]
ignore = [
    "drupal/global-drupal-call",
    { code = "drupal/entity-storage-injection", in = "src/Legacy/" },
]

An entry with a pattern drops the issues whose text matches a regular expression. The text is the message, the notes, the help and the annotations of the issue. code and in narrow the entry further:

[analyzer]
ignore = [
    { code = "unsafe-instantiation", pattern = "new static\\(\\)" },
]

What fails a run

phpcs exits with an error when it reports a warning or an error. mago lint on its own fails only on an error, and many of its rules report a warning, help or a note. drupal.mago.toml sets minimum-fail-level = "warning", and gives the Mago rules that stand in for a Coder sniff the level used by Coder. So a report that fails phpcs fails mago lint too.

The Mago rules that are on and don't port any sniff, such as strict-behavior and str-contains, fail the run as well when they report a warning. Turn a rule off, or set its level, in the project's mago.toml:

[linter]
# Fail on errors only.
minimum-fail-level = "error"

[linter.rules]
strict-behavior = { level = "help" }

mago analyze has its own minimum-fail-level under [analyzer], which isn't set by drupal.mago.toml.

Checking Drupal core

drupal-core.mago.toml is for a core checkout. It extends drupal.mago.toml and adds:

  • The files that core's phpcs.xml.dist checks: core/ and composer/, with .test files, and vendor/ read for its symbols.
  • The files that phpcs doesn't check, left out of the formatter and the linter: the paths excluded by phpcs.xml.dist, and the files marked with // phpcs:ignoreFile or // @codingStandardsIgnoreFile, which Mago's own rules don't read.
  • The classes that core's phpstan.neon.dist leaves out because they're broken on purpose, left out of the analyzer. The one that Mago reports a semantics error for is left out of the linter too, since that error doesn't have a rule to turn off.
  • The worker with --phpcs-ignores and --phpstan-ignores, so that core's phpcs and PHPStan comments drop the issues of the extension's rules, see phpcs and PHPStan comments.
  • The rules and the analyzer checks that core doesn't run, turned off.

Which rules are off

Core's own phpcs.xml.dist asks for less than Drupal's standard. drupal-core.mago.toml follows the file of Drupal 11.x, which runs Coder 9, and turns off the rules whose checks core turns off or doesn't run, Mago's own rules included. That covers Mago's general advice, such as strict-behavior and inline-variable-return, which doesn't port any sniff that core runs. Where phpcs.xml.dist leaves paths out of a sniff, such as the tests directories out of the line length check, the file leaves them out of the rules that port it. It also ignores five analyzer checks that are about modules that use core's API. The comments in the file say why.

A rule that stays on can check more than the sniff that it ports, such as drupal/hook-comment on a #[Hook] method and drupal/doc-see-tag in a class docblock. Those reports fail the run on core, where phpcs passes.

A rule that the file turns off can be turned back on in the checkout's mago.toml:

extends = "vendor/drupal/mago/drupal-core.mago.toml"
php-version = "8.3"

[linter.rules]
"drupal/inline-comment-punctuation" = { enabled = true }

An analyzer check stays ignored, since Mago adds the ignore list of mago.toml to the one of the file that it extends.

Two rules follow core's directories by themselves, since a rule's exclude setting can't name the only directories to check. In the files of Drupal core itself, missing-summary and missing-function-comment run only in the directories where core's phpcs.xml.dist runs them. A file is core's when one of its directories is named core and holds lib/Drupal.php. drupal/unknown-service checks every service id in core's files, since core looks up only its own services.

A project that starts from core's phpcs.xml.dist but isn't core can copy the [linter.rules] entries of drupal-core.mago.toml, see Turning rules off.