diff --git a/.specify/memory/constitution.md b/.specify/memory/constitution.md new file mode 100644 index 0000000..9db590b --- /dev/null +++ b/.specify/memory/constitution.md @@ -0,0 +1,158 @@ + + +# Project Constitution — knplabs/phpstan-rules + +**Constitution Version:** 1.0.0 +**Ratification Date:** 2026-07-15 +**Last Amended Date:** 2026-07-15 +**Maintainers:** KnpLabs organization + +--- + +## Project Identity + +**Name:** knplabs/phpstan-rules +**Package type:** PHPStan extension (open-source, MIT) +**Purpose:** Provide PHPStan static-analysis rules shared across KnpLabs organization projects, +made available to the wider PHP community as a Composer package. +**Primary audience:** PHP developers who want to enforce organization-wide coding conventions via +PHPStan. + +--- + +## Core Principles + +### Principle 1 — Broad Compatibility + +Every release MUST remain installable on the widest reasonable range of PHP and PHPStan versions +to maximize adoption. Dropping a supported version is a MAJOR version bump and MUST be explicitly +justified. + +**Non-negotiable rules:** +- `composer.json` `require.php` MUST target `^8.2` or broader (never pin to a single patch). +- `composer.json` `require.phpstan/phpstan` MUST support at least the current stable major series + and, where API-compatible, the previous major. +- CI MUST gate on every declared PHP minor version (currently 8.2, 8.3, 8.4, 8.5). +- No rule implementation MUST use PHP APIs unavailable in the declared minimum version. +- Dependencies MUST be kept minimal; new `require` entries need explicit justification. + +**Rationale:** Restrictive version constraints are the primary reason PHPStan extensions go +un-adopted. A wide compatibility matrix lowers the barrier to entry and encourages community +contributions. + +--- + +### Principle 2 — Test Coverage per Rule + +Every PHPStan rule MUST ship with a dedicated `PHPUnit` / `PHPStan\Testing\RuleTestCase` test +covering both triggering and non-triggering cases. + +**Non-negotiable rules:** +- Each rule class in `src/Rules//` MUST have a corresponding test in + `tests/Rules//` following the naming convention `Test.php`. +- Each test MUST include at least one fixture file in `tests/Rules//fixtures/` that + exercises both the positive (violation) and negative (no violation) paths. +- New rules MUST NOT be registered in `extension.neon` until their test passes on all declared + PHP versions. +- CI MUST run the full test suite (`composer test`) and it MUST be green before merging. + +**Rationale:** Rules without tests are liabilities — they cannot be refactored safely and may +produce false positives or negatives undetected across PHPStan/PHP upgrades. + +--- + +### Principle 3 — Documentation per Rule + +Every PHPStan rule MUST be documented in `README.md` before it is released. + +**Non-negotiable rules:** +- Each rule entry in `README.md` MUST include: + - The rule identifier (e.g., `clock.disallowDateTimeNow`). + - A one-sentence summary of what the rule enforces and why. + - A "Triggers on" code block with at least one violating example. + - A "Does not trigger on" code block with at least one passing example. + - A "Recommended fix" section showing the preferred alternative pattern. +- Documentation MUST be kept in sync with rule behavior; a PR that changes a rule's logic + MUST also update its documentation. +- Rules that are not yet documented MUST NOT appear in a stable release. + +**Rationale:** Without clear documentation, developers cannot evaluate whether a rule is +appropriate for their project, and they cannot understand why a violation was flagged. + +--- + +### Principle 4 — Open-Source Quality Standards + +All source files MUST meet the project's code-quality baseline so that community contributors +can work effectively. + +**Non-negotiable rules:** +- All PHP source files MUST declare `strict_types=1`. +- Code MUST conform to PSR-12 enforced by `php-cs-fixer` (`composer cs-check` MUST pass). +- The codebase itself MUST pass PHPStan analysis at the configured level (`composer phpstan` + MUST pass). +- PHPStan baseline (`phpstan-baseline.neon`) MUST be kept as small as possible; new suppressions + MUST be justified in the PR description. +- Rule classes MUST be `final readonly` where PHP version and design allow. + +**Rationale:** Consistent code style and static analysis lower the cognitive load for contributors +and prevent quality regressions from being introduced silently. + +--- + +### Principle 5 — Single-Responsibility Rule Design + +Each rule class MUST enforce exactly one logical constraint. + +**Non-negotiable rules:** +- A rule MUST be scoped to a single AST node type (one `getNodeType()` return value per class). +- If two related constraints share an AST node, they MUST be implemented as separate classes + unless their logic is inseparably coupled (requires explicit justification in the PR). +- Rule namespaces (`src/Rules//`) MUST group rules by domain, not by node type. +- The rule's error message MUST be actionable: it MUST state what to do instead, not only + what was found wrong. + +**Rationale:** Small, focused rules are easier to test, easier to selectively disable in +downstream projects, and easier for contributors to understand and maintain. + +--- + +## Governance + +### Amendment Procedure + +1. Open a GitHub issue or PR describing the proposed change and its rationale. +2. At least one maintainer MUST review and approve the change. +3. The `CONSTITUTION_VERSION` MUST be bumped according to the semantic rules below. +4. `LAST_AMENDED_DATE` MUST be updated to the merge date. +5. The Sync Impact Report (HTML comment at top of this file) MUST be refreshed. + +### Versioning Policy + +- **MAJOR** bump: backward-incompatible governance change — removing a principle, redefining + a non-negotiable rule in a stricter or incompatible way. +- **MINOR** bump: new principle added, new mandatory section introduced, materially expanded + guidance. +- **PATCH** bump: clarification, wording improvement, typo fix, non-semantic refinement. + +### Compliance Review + +- Constitution compliance MUST be assessed during each PR review. +- Each speckit feature cycle (specify → plan → tasks → implement) MUST reference the active + constitution version. +- If a task or implementation choice would violate a principle, the violation MUST be escalated + before merging — not suppressed silently. diff --git a/.specify/templates/plan-template.md b/.specify/templates/plan-template.md new file mode 100644 index 0000000..bfdf414 --- /dev/null +++ b/.specify/templates/plan-template.md @@ -0,0 +1,60 @@ +# Implementation Plan — [FEATURE_NAME] + +**Constitution version:** 1.0.0 +**Spec reference:** `.specify/specs/[feature-slug]/spec.md` +**Author:** [AUTHOR] +**Date:** [DATE] + +--- + +## Constitution Check + +Before finalizing this plan, confirm compliance with the active constitution: + +| Principle | Check | +|-----------|-------| +| 1 — Broad Compatibility | [ ] No new PHP/PHPStan version constraint tightening | +| 2 — Test Coverage per Rule | [ ] Test class and fixture planned | +| 3 — Documentation per Rule | [ ] README section planned | +| 4 — Open-Source Quality Standards | [ ] strict_types, PSR-12, PHPStan passing | +| 5 — Single-Responsibility Rule Design | [ ] One node type per class confirmed | + +--- + +## Approach + +[Describe the chosen implementation approach and why it was selected over alternatives.] + +### Alternatives considered + +| Alternative | Rejected because | +|-------------|-----------------| +| [option] | [reason] | + +--- + +## File Inventory + +| File | Action | Notes | +|------|--------|-------| +| `src/Rules/[Namespace]/[RuleName].php` | Create | Core rule class | +| `tests/Rules/[Namespace]/[RuleName]Test.php` | Create | PHPUnit test | +| `tests/Rules/[Namespace]/fixtures/[name].php` | Create | Test fixture | +| `extension.neon` | Edit | Register rule service | +| `README.md` | Edit | Add rule documentation | + +--- + +## Edge Cases & Risks + +- [List known edge cases the implementation must handle] +- [List risks, e.g., PHPStan API instability, PHP version quirks] + +--- + +## Validation Steps + +1. `docker compose run --rm php vendor/bin/phpunit tests/Rules/[Namespace]/[RuleName]Test.php` +2. `docker compose run --rm php composer phpstan` +3. `docker compose run --rm php composer cs-check` +4. Manual review of fixture file to confirm positive/negative cases. diff --git a/.specify/templates/spec-template.md b/.specify/templates/spec-template.md new file mode 100644 index 0000000..b6bf747 --- /dev/null +++ b/.specify/templates/spec-template.md @@ -0,0 +1,75 @@ +# Feature Specification — [FEATURE_NAME] + +**Constitution version:** 1.0.0 +**Status:** [draft | review | approved] +**Author:** [AUTHOR] +**Date:** [DATE] + +--- + +## Overview + +[One paragraph describing the feature, the problem it solves, and why it belongs in this package.] + +--- + +## Scope + +### In scope +- [What this feature covers] + +### Out of scope +- [What this feature explicitly does not cover] + +--- + +## Rule Definition (if applicable) + +**Rule identifier:** `[namespace.ruleName]` +**Node type:** `[PhpParser\Node\...]` +**Namespace / directory:** `src/Rules/[Namespace]/` + +### Triggers on +```php +// [example code that should produce a violation] +``` + +### Does not trigger on +```php +// [example code that should NOT produce a violation] +``` + +### Error message +> [Exact error message string, must be actionable] + +### Recommended fix +[Describe the pattern the developer should use instead.] + +--- + +## Compatibility Requirements + + +- Minimum PHP version affected: [e.g., 8.2+] +- PHPStan version requirements: [e.g., ^2.0] +- Any new `composer.json` dependencies: [none | list with justification] + +--- + +## Acceptance Criteria + + +- [ ] Rule class exists at `src/Rules/[Namespace]/[RuleName].php` and is `final readonly`. +- [ ] Rule is registered in `extension.neon`. +- [ ] Test class exists at `tests/Rules/[Namespace]/[RuleName]Test.php`. +- [ ] Fixture file covers at least one triggering and one non-triggering case. +- [ ] `composer test` passes on PHP 8.2, 8.3, 8.4, 8.5. +- [ ] `composer phpstan` passes with no new baseline suppressions (or suppressions justified). +- [ ] `composer cs-check` passes. +- [ ] `README.md` updated with rule documentation (identifier, summary, examples, fix). + +--- + +## Open Questions + +- [List any unresolved design or scope questions] diff --git a/.specify/templates/tasks-template.md b/.specify/templates/tasks-template.md new file mode 100644 index 0000000..a6667d3 --- /dev/null +++ b/.specify/templates/tasks-template.md @@ -0,0 +1,59 @@ +# Tasks — [FEATURE_NAME] + +**Constitution version:** 1.0.0 +**Plan reference:** `.specify/specs/[feature-slug]/plan.md` +**Author:** [AUTHOR] +**Date:** [DATE] + +--- + +## Task List + +Tasks are ordered by dependency. Mark `[x]` when complete. + +### Phase 1 — Rule Implementation + +- [ ] **T1** — Create rule class `src/Rules/[Namespace]/[RuleName].php` + - Implement `Rule<[NodeType]>` + - Add `getNodeType()` returning `[NodeType]::class` + - Add `processNode()` with violation detection logic + - Ensure `final readonly` class modifier + - Add `declare(strict_types=1)` + +- [ ] **T2** — Register rule in `extension.neon` + - Add service entry with `phpstan.rules.rule` tag + +### Phase 2 — Testing (Constitution Principle 2) + +- [ ] **T3** — Create fixture file `tests/Rules/[Namespace]/fixtures/[name].php` + - Include at least one triggering case (with inline comment marking expected violation) + - Include at least one non-triggering case + +- [ ] **T4** — Create test class `tests/Rules/[Namespace]/[RuleName]Test.php` + - Extend `PHPStan\Testing\RuleTestCase<[RuleName]>` + - Implement `getRule()` returning a new instance of the rule + - Add `testRule()` calling `$this->analyse()` with fixture path and expected errors + +- [ ] **T5** — Verify test suite passes + - `docker compose run --rm php composer test` — green on all PHP versions + +### Phase 3 — Documentation (Constitution Principle 3) + +- [ ] **T6** — Update `README.md` + - Add rule section with identifier, summary, "Triggers on", "Does not trigger on", + and "Recommended fix" + +### Phase 4 — Quality Gate (Constitution Principle 4) + +- [ ] **T7** — Verify static analysis + - `docker compose run --rm php composer phpstan` — passes, no new baseline entries + +- [ ] **T8** — Verify code style + - `docker compose run --rm php composer cs-check` — passes + +--- + +## Definition of Done + +All tasks T1–T8 checked AND a PR reviewer has confirmed compliance with the constitution +(all five principles satisfied).