Skip to content

Commit 0583d96

Browse files
authored
Merge pull request #5 from bordoni/feature/phpstan
chore(tooling): add PHPStan level 5 to dev tooling + CI
2 parents 06b7ba9 + 2653cac commit 0583d96

15 files changed

Lines changed: 460 additions & 32 deletions

File tree

.github/workflows/test.yml

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,48 @@ jobs:
3535
- name: Run PHPCS
3636
run: vendor/bin/phpcs
3737

38+
phpstan:
39+
name: PHPStan
40+
runs-on: ubuntu-latest
41+
steps:
42+
- uses: actions/checkout@v6
43+
44+
- name: Setup PHP
45+
uses: shivammathur/setup-php@v2
46+
with:
47+
php-version: '8.2'
48+
tools: composer
49+
coverage: none
50+
51+
- name: Setup Node
52+
uses: actions/setup-node@v6
53+
with:
54+
node-version-file: '.nvmrc'
55+
56+
- name: Setup bun
57+
uses: oven-sh/setup-bun@v2
58+
59+
# Composer install MUST run scripts here (unlike the PHPCS job above)
60+
# so Strauss generates the prefixed `WorkOS\Vendor\…` classes that
61+
# PHPStan needs to resolve types.
62+
- name: Install PHP dependencies (with Strauss)
63+
run: composer install --no-interaction --prefer-dist
64+
env:
65+
COMPOSER_NO_AUDIT: 1
66+
67+
# Build the JS bundles so the `build/*.asset.php` files exist —
68+
# PHPStan statically resolves the `include $asset_file` paths in
69+
# Admin/Settings.php, UI/Controller.php, etc. and would otherwise
70+
# fail with `path.notExists` even though every callsite is guarded
71+
# by `file_exists()`.
72+
- name: Install JS dependencies + build
73+
run: |
74+
bun install --frozen-lockfile
75+
bun run build
76+
77+
- name: Run PHPStan
78+
run: composer phpstan -- --no-progress --error-format=github
79+
3880
tests-php:
3981
name: PHP Tests (PHP ${{ matrix.php }})
4082
runs-on: ubuntu-latest

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,10 @@
99
*.cache
1010
*.log
1111

12+
# PHPStan baseline — fix-everything policy; do not commit a baseline
13+
# file without team discussion (see AGENTS.md > Static Analysis).
14+
/phpstan-baseline.neon
15+
1216
# Lock files
1317
package-lock.json
1418

AGENTS.md

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,8 @@ Per-environment constants (take priority over generic):
180180
| `bun.lock` | Locked dependency graph (committed) |
181181
| `tsconfig.json` | TypeScript config (strict, `jsx: react-jsx`, `noEmit: true`) |
182182
| `webpack.config.js` | Extends `@wordpress/scripts` default config with authkit + admin-profiles entries |
183+
| `phpstan.neon.dist` | PHPStan config (level 5, scans `src/` + `integration-workos.php` + `uninstall.php`, `phpVersion: 70400`, Strauss vendor resolved via `vendor/autoload.php` in `scanFiles`) |
184+
| `phpstan/stubs.php` | Symbol stubs for `WORKOS_*` runtime-defined constants so PHPStan can resolve them statically — never executed |
183185

184186
## Build System
185187

@@ -197,6 +199,7 @@ bun run start # Development with watch
197199
bun run lint:ts # Type-check TypeScript (tsc --noEmit)
198200
bun run lint:php # Lint PHP via PHPCS
199201
bun run lint:php:fix # Auto-fix PHP lint issues
202+
composer phpstan # Static analysis (PHPStan level 5, --memory-limit=1G)
200203
```
201204

202205
`@wordpress/scripts` v30 transpiles `.ts` / `.tsx` natively via its default
@@ -297,3 +300,47 @@ Use the global `/slic` skill for comprehensive guidance on test structure, envir
297300

298301
- PHP follows WordPress Coding Standards via PHPCS/WPCS
299302
- Run `composer lint` / `composer lint:fix` for PHP linting
303+
304+
## Static Analysis (PHPStan)
305+
306+
PHPStan analyses the plugin source at **level 5** and is gated in CI as
307+
a required check on PRs to `main`. Config: `phpstan.neon.dist`.
308+
309+
### Stack
310+
311+
- `phpstan/phpstan` ^2 — analyzer, in `composer require-dev`
312+
- `szepeviktor/phpstan-wordpress` — WordPress core stubs + WP-aware
313+
inference for `apply_filters`, `wp_remote_request`, hook signatures
314+
- `php-stubs/wp-cli-stubs``WP_CLI`, `WP_CLI_Command`,
315+
`WP_CLI\Formatter` for `src/WorkOS/CLI/*`
316+
- `phpstan/extension-installer` — auto-registers neon files from
317+
installed extensions (no manual `includes:` wiring)
318+
319+
### Scope
320+
321+
- Scanned: `src/`, `integration-workos.php`, `uninstall.php`
322+
- `phpVersion: 70400` so PHP 7.4 syntax mistakes can't slip past on
323+
PHP 8.x runners
324+
- `treatPhpDocTypesAsCertain: false` — narrowing is required when a
325+
hook caller may pass something other than the documented type
326+
- Strauss-prefixed `WorkOS\Vendor\…` resolved via `vendor/autoload.php`
327+
in `scanFiles`. The PHPStan CI job therefore must run
328+
`composer install` WITH scripts (so Strauss runs and prefixed
329+
classes exist on disk) — that's the key difference from the PHPCS
330+
CI job, which uses `--no-scripts` + a stub `vendor/prefixed/autoload.php`.
331+
- WP-CLI stubs (`vendor/php-stubs/wp-cli-stubs/wp-cli-*.php`) and
332+
`phpstan/stubs.php` (project-local `WORKOS_*` constants) are loaded
333+
via `scanFiles`. Not bootstrap files — just symbol discovery.
334+
335+
### Policy
336+
337+
- **No baseline.** Findings get fixed in the PR that introduces them.
338+
`composer phpstan:baseline` exists as a safety hatch for
339+
exceptional cases but the resulting `phpstan-baseline.neon` should
340+
not be committed without discussion.
341+
- **Tests are not analyzed yet.** Codeception's wp-browser stubs are
342+
partial; revisit once `phpstan/phpstan-phpunit` integration is
343+
worth the noise.
344+
- **Strict-rules extension is intentionally not enabled.** Level 5 is
345+
the floor we're committing to first; reconsider strict-rules as a
346+
follow-up when this is stable.

README.md

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -791,6 +791,38 @@ composer lint:fix # PHP (auto-fix)
791791
bun run lint:ts # TypeScript
792792
```
793793

794+
### Static Analysis (PHPStan)
795+
796+
PHPStan runs at **level 5** against `src/`, `integration-workos.php`,
797+
and `uninstall.php`. The config lives in `phpstan.neon.dist` and is
798+
enforced by the `PHPStan` GitHub Actions job — PRs to `main` cannot
799+
merge while it's red.
800+
801+
```bash
802+
composer phpstan # Run analysis (--memory-limit=1G)
803+
composer phpstan:baseline # Generate phpstan-baseline.neon (fix-everything policy: do not commit)
804+
```
805+
806+
The stack:
807+
808+
- `phpstan/phpstan` ^2 — analyzer
809+
- `szepeviktor/phpstan-wordpress` — WordPress core stubs + WP-aware
810+
inference (handles `apply_filters`, `wp_remote_request`, hook
811+
signatures)
812+
- `php-stubs/wp-cli-stubs``WP_CLI`, `WP_CLI_Command`,
813+
`WP_CLI\Formatter`, etc. for the `src/WorkOS/CLI/*` commands
814+
- `phpstan/extension-installer` — auto-registers extension neon files
815+
816+
`phpstan/stubs.php` declares the `WORKOS_*` constants that
817+
`Plugin::init()` defines at runtime so PHPStan can resolve them at
818+
parse time. Strauss-prefixed `WorkOS\Vendor\…` classes are picked up
819+
automatically via `vendor/autoload.php` in `scanFiles`.
820+
821+
**Policy: no baseline.** Findings must be fixed in the PR that
822+
introduces them. The `composer phpstan:baseline` script exists as a
823+
safety hatch, but the resulting file should not be committed without
824+
discussion.
825+
794826
### Architecture
795827

796828
The plugin uses a DI container (di52) with a feature-controller pattern:

composer.json

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,12 @@
2424
"codeception/module-filesystem": "^3.0",
2525
"dealerdirect/phpcodesniffer-composer-installer": "^1.0",
2626
"lucatume/wp-browser": "^4.5",
27+
"php-stubs/wp-cli-stubs": "^2.11",
2728
"phpcompatibility/phpcompatibility-wp": "^2.1",
29+
"phpstan/extension-installer": "^1.4",
30+
"phpstan/phpstan": "^2.0",
2831
"squizlabs/php_codesniffer": "^3.8",
32+
"szepeviktor/phpstan-wordpress": "^2.0",
2933
"wp-coding-standards/wpcs": "^3.0"
3034
},
3135
"autoload": {
@@ -36,14 +40,17 @@
3640
},
3741
"config": {
3842
"allow-plugins": {
39-
"dealerdirect/phpcodesniffer-composer-installer": true
43+
"dealerdirect/phpcodesniffer-composer-installer": true,
44+
"phpstan/extension-installer": true
4045
},
4146
"sort-packages": true
4247
},
4348
"scripts": {
4449
"test:wpunit": "vendor/bin/codecept run wpunit",
4550
"lint": "phpcs",
4651
"lint:fix": "phpcbf",
52+
"phpstan": "phpstan analyse --memory-limit=1G",
53+
"phpstan:baseline": "phpstan analyse --memory-limit=1G --generate-baseline",
4754
"strauss-install": [
4855
"test -f ./bin/strauss.phar || (mkdir -p bin && curl -o bin/strauss.phar -L https://github.com/BrianHenryIE/strauss/releases/download/0.26.5/strauss.phar)"
4956
],

0 commit comments

Comments
 (0)