Skip to content

Add inner-bucket tag ordering to DocBlockTagOrderSniff#60

Merged
dereuromark merged 2 commits into
masterfrom
feature/docblock-tag-inner-order
May 12, 2026
Merged

Add inner-bucket tag ordering to DocBlockTagOrderSniff#60
dereuromark merged 2 commits into
masterfrom
feature/docblock-tag-inner-order

Conversation

@dereuromark
Copy link
Copy Markdown
Contributor

@dereuromark dereuromark commented May 9, 2026

Summary

Extends DocBlockTagOrderSniff with an opt-in innerOrder property that orders tags within a tag-type bucket. Step 1 (PR #59) ordered the buckets themselves; this is Step 2: ordering within them.

Default innerOrder = [] keeps existing behavior unchanged. Configured via XML:

<rule ref="PhpCollective.Commenting.DocBlockTagOrder">
    <properties>
        <property name="innerOrder" type="array">
            <element key="@method"
                     value="newEmptyEntity,newEntity,newEntities,get,find*,findOrCreate,patchEntity,patchEntities,save,saveOrFail,saveMany*,delete,deleteOrFail,deleteMany*"/>
            <element key="@property" value=""/>
            <element key="@property-read" value=""/>
            <element key="@property-write" value=""/>
        </property>
    </properties>
</rule>

Two recipes

  • Method tag CRUD ordering — prefix list lifts well-known operations (newEmptyEntity, newEntity, get, save, ...) to the top in a configured order; custom methods float to the bottom alphabetically.
  • Property tag alphabetization — an empty pattern list sorts the entire bucket alphabetically. Intended for unordered lists where ordering carries no semantics: association annotations on Table classes, helper annotations on View classes, IDE-helper-generated lookup blocks.

Scoping for CakePHP entities

The empty-pattern recipe is not appropriate for entity column docblocks. CakePHP entities list their properties in DB schema order (id first, logical fields together, timestamps last), and that order is meaningful for review and stays in sync with re-runs of cakephp-ide-helper. Alphabetizing breaks column groupings (lat/lng/location, beginning/end, lft/rght) and creates infinite churn against IDE-helper regeneration.

The separate InnerOrderInvalid error code makes this easy to scope per path without losing bucket-level ordering on the same files:

<rule ref="PhpCollective.Commenting.DocBlockTagOrder.InnerOrderInvalid">
    <exclude-pattern>*/src/Model/Entity/*</exclude-pattern>
</rule>

Tables, views, controllers still receive the inner ordering. Tested on a real CakePHP sandbox: 51 files flagged initially; scoping the exclude leaves 18 net-positive fixes — for example, a randomly-placed find method annotation pulled up to its canonical bake slot in Table classes — and skips ~30 entity rewrites that would have destroyed schema-meaningful column order.

How it works

Within each configured bucket, every tag is scored by first-matching prefix (lowest wins; unmatched gets PHP_INT_MAX and floats to the bottom), then sorted alphabetically by extracted subject within each score class.

Subject extraction handles return types with generics, union types, the static modifier, missing return types, and trailing-bareword malformed lines.

A separate InnerOrderInvalid error code (alongside the existing OrderInvalid) lets users suppress one without losing the other — see the entity-scoping recipe above.

Refs cakephp/cakephp-codesniffer#434.

Extends the sniff with an opt-in innerOrder property that sorts tags
within a tag-type bucket (e.g. ordering of method tags within the
method group) using user-configured prefix patterns plus alphabetical
tiebreaker.

Default is empty - existing behavior unchanged unless configured. Two
recipes covered by tests:

- method tag CRUD ordering: prefix list lifts well-known operations
  (newEmptyEntity, newEntity, get, save, ...) to the top in a
  configured order, custom methods float to the bottom alphabetically.
- property tag alphabetization: empty pattern list sorts the entire
  bucket alphabetically, which is the typical recipe for association
  lists generated by IDE helpers.

Subject extraction handles return-type expressions with generics and
union types, the static modifier, missing return types, and trailing-
bareword malformed lines. Errors use a separate InnerOrderInvalid
code so they can be suppressed independently from the existing
OrderInvalid bucket-level errors.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds an opt-in innerOrder configuration to DocBlockTagOrderSniff to enforce deterministic ordering of tags within a tag-type bucket (e.g. ordering @method lines by CRUD prefixes, or alphabetizing @property association lists), alongside new fixtures and tests.

Changes:

  • Add innerOrder property and subject-extraction/scoring logic to enable within-bucket ordering.
  • Emit a separate InnerOrderInvalid fixable error code for inner-order violations.
  • Add 4 new fixture pairs and corresponding PHPUnit tests covering property/method recipes and edge cases.

Reviewed changes

Copilot reviewed 10 out of 10 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniff.php Implements inner-bucket ordering (detection + fixer) and introduces InnerOrderInvalid.
tests/PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniffTest.php Adds tests for inner ordering recipes, edge cases, and combined outer+inner fixes.
tests/_data/DocBlockTagOrder/inner-property.*.php Fixture for alphabetizing @property bucket.
tests/_data/DocBlockTagOrder/inner-method.*.php Fixture for CRUD-priority + alphabetical inner ordering in @method bucket.
tests/_data/DocBlockTagOrder/inner-method-edges.*.php Fixture for edge-case @method line shapes (generics/unions/static/malformed).
tests/_data/DocBlockTagOrder/inner-combined.*.php Fixture for combined outer bucket ordering + inner ordering in one fixer pass.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniff.php
Comment thread PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniff.php
Comment thread PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniff.php Outdated
Comment thread PhpCollective/Sniffs/Commenting/DocBlockTagOrderSniff.php Outdated
…bjects to bottom

- The -1 bucket in fixOrder() holds tags not present in the outer
  order list, which means it can contain a mix of tag types. The
  inner-order code path treats `$entries[0]['tag']` as the bucket's
  tag name and applies that tag's prefix list to every entry, which
  produces wrong subject extraction across mixed types. Skip the -1
  bucket entirely — the alphabetical-by-content sort applied earlier
  already gives those entries a stable order.
- The inner-order sort key in checkInnerOrder() previously paired
  `[$score, (string)$subject]`; for null subjects (malformed/
  unparseable lines) (string)null === '' sorts before every real
  string within the same score class. Insert a presence flag in the
  tuple so null subjects sort last, matching the "unmatched floats to
  the bottom" contract.
- The corresponding comparator in fixOrder() had the same hazard;
  rewrite to explicitly push null-subject entries to the bottom of
  their score class and to fall back to original line content as the
  stable tiebreaker when both subjects are null.
- Note in the property docblock that innerOrder is intentionally
  class/interface/trait-only — per-method @param/@return tags are
  positional and have no within-bucket subject to reorder against.
@dereuromark dereuromark merged commit 6433ed1 into master May 12, 2026
4 checks passed
@dereuromark dereuromark deleted the feature/docblock-tag-inner-order branch May 12, 2026 00:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants