Skip to content

Add PHPUnit test to enforce rule registration conventions #971

@mglaman

Description

@mglaman

Problem

New rules must follow a set of conventions, but there is nothing enforcing them automatically:

  1. Always registered via conditionalTags, never directly in rules:
  2. Default value in extension.neon must be false (opt-in)
  3. Must be enabled in bleedingEdge.neon while opt-in

These are easy to miss, as demonstrated by entityStorageDirectInjectionRule shipping without being in bleedingEdge.neon.

Goal

All rules should be toggleable. Going forward, a rule graduates to the default ruleset by flipping its extension.neon default from false to true — it stays in conditionalTags and remains opt-out. Direct rules: registration (not toggleable) is reserved for the existing legacy rules and must not grow.

Proposed solution

Add a PHPUnit test (e.g. tests/src/RuleConventionsTest.php) that parses rules.neon, extension.neon, and bleedingEdge.neon using nette/neon (already available as a transitive dependency) and asserts:

For all rules in conditionalTags:

  • A corresponding parameter exists in extension.neon › parameters › drupal › rules
  • If the default is false, the rule must also be enabled in bleedingEdge.neon

For rules in rules: directly:

  • The class must be on a fixed allowlist of pre-existing legacy rules
  • Any class not on the allowlist fails the test, preventing new always-on, non-toggleable rules from being added

Convention summary

Location Default in extension.neon Meaning Required in bleedingEdge.neon
conditionalTags false Opt-in, not yet default Yes
conditionalTags true Graduated to default, still toggleable No
rules: directly Legacy, not toggleable — no new additions

The test always runs — it is cheap and passes silently until a convention is violated.

Implementation prompt

Implement `tests/src/RuleConventionsTest.php` in the phpstan-drupal project.

The test parses three neon files using `Nette\Neon\Neon::decode()` (available via the
phpstan/phpstan phar's bundled nette/neon, or directly from the nette/neon package in vendor):
- `rules.neon` — service definitions and conditionalTags for all rules
- `extension.neon` — parameter defaults under `parameters.drupal.rules`
- `bleedingEdge.neon` — opt-in overrides

The test class should extend `PHPUnit\Framework\TestCase` and contain three test methods:

1. `testNewRulesAreNotDirectlyRegistered`
   Parse the `rules:` list from `rules.neon`. Assert that every class in that list is present
   in a hardcoded allowlist of legacy always-on rules. Fail with a clear message if a class
   not on the allowlist is found — this prevents new non-toggleable rules from being added.

2. `testConditionalRulesHaveFalseDefaultInExtensionNeon`
   Collect every class key from `conditionalTags` in `rules.neon` that maps to a
   `phpstan.rules.rule` tag. Resolve the parameter name from the tag value
   (e.g. `%drupal.rules.fooBarRule%` → `fooBarRule`). Assert that each parameter exists
   under `parameters.drupal.rules` in `extension.neon` with a value of `false`.

3. `testOptInRulesAreEnabledInBleedingEdge`
   Using the same set of conditional rules, check the default in `extension.neon`. For any
   rule whose default is `false`, assert it is present in `bleedingEdge.neon` under
   `parameters.drupal.rules` with a value of `true`. Rules whose default is `true`
   (graduated) are skipped — they are already on by default and do not need to be in
   bleedingEdge.neon.

The neon files are at the project root. Resolve their paths relative to the test file using
`__DIR__` (e.g. `__DIR__ . '/../../../rules.neon'`).

Note: `tests/fixtures/config/phpunit-drupal-phpstan.neon` includes `bleedingEdge.neon`, so
all opt-in rules are active during the full test suite — the conventions test does not need
to load any fixture config, it only reads the neon files as raw data.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions