Skip to content

Tools: Move the PHPStan report script into a tools/phpstan workspace - #86222

Merged
westonruter merged 2 commits into
trunkfrom
tools/phpstan-report-workspace
Nov 3, 2026
Merged

westonruter merged 2 commits into
trunkfrom
tools/phpstan-report-workspace

Conversation

@westonruter

@westonruter westonruter commented Nov 3, 2026 •

Copy link
Copy Markdown
Member

What?

Follow-up to #66693, addressing #66693 (comment).

This moves the script that reports the PHPStan results in CI from .github/workflows/scripts/phpstan-report.js to tools/phpstan/report.ts, in a new @wordpress/phpstan-tools workspace, so that it is linted and type checked.

Why?

The script lived outside every workspace, so neither npm run lint:js nor npm run typecheck covered it, and it had nowhere to declare a dependency should it ever need one. tools/phpstan already holds the rest of the PHPStan tooling, so it becomes the workspace.

How?

  • Workspace: tools/phpstan gets a package.json (@wordpress/phpstan-tools, private, with @types/node as its only dependency), a tsconfig.json extending @wordpress/monorepo-tools/tsconfig/node.base.json like tools/pr-meta and tools/validation, a reference in the root tsconfig.json, its entries in package-lock.json, a report script that runs report.ts, and a README.md describing what the directory holds.
  • TypeScript: the JSDoc types are rewritten as TypeScript, with an interface for the part of the PHPStan JSON report that the script reads, and require( 'node:fs' ) becomes an ES import. Since npm run runs the script in the workspace directory, the paths in the report are made relative to the repository root, found from the script's own location when GITHUB_WORKSPACE is not set, and a relative path to the report is resolved from INIT_CWD, the directory that npm was run in. Otherwise the logic is unchanged: on the same report, the old and new scripts print the same annotations, write the same job summary, and exit with the same status.
  • CI: the PHP static analysis job runs the report with npm run --workspace @wordpress/phpstan-tools report, as other workflows do for the other tools. Node runs the script by stripping its types, as with tools/pr-meta, so the job now sets up the Node version in .nvmrc before the report step, rather than relying on the runner's default, followed by ./.github/setup-npm, since npm run requires the npm version in the devEngines of package.json. The script imports nothing outside Node, and npm run --workspace only reads the package.json files, so the job still installs no npm dependencies. The PHPStan result cache key hashes only the .neon, .php, and .stub files in tools/phpstan and tools/phpstan/src/**, so the new files do not invalidate it.
  • CODEOWNERS: the entry for the old path, added in Add codeowner for PHPStan tooling #86204, is removed, since the existing /tools/phpstan entry now covers the script.

Testing Instructions

  1. Run npm run typecheck -- tools/phpstan/tsconfig.json and npm run lint:js -- tools/phpstan/report.ts, and confirm that both pass.
  2. Introduce a type error in tools/phpstan/report.ts, such as passing a number to escapeHtml(), and confirm that npm run typecheck reports it.
  3. Run composer phpstan -- --error-format=json --no-progress > report.json, then npm run --workspace @wordpress/phpstan-tools report -- report.json, and confirm that it prints an ::error annotation for each error, or nothing when there are none.
  4. Confirm that the PHP static analysis job passes in CI, and that its job summary lists the results as before.

Testing Instructions for Keyboard

Not applicable, since there are no UI changes.

Use of AI Tools

This was done with Claude Code (Claude Opus 5.5, high effort): moving the script and converting it to TypeScript, setting up the workspace and the CI step, comparing the output of the old and new scripts, and drafting the commit message and this description, under the direction and review of @westonruter.

The script that reports the PHPStan results in CI lived in
`.github/workflows/scripts`, outside every workspace, so it was neither
linted nor type checked, and had nowhere to declare a dependency should
it ever need one.

It is now `tools/phpstan/report.ts`, in a new `@wordpress/phpstan-tools`
workspace alongside the rest of the PHPStan tooling, with its JSDoc types
rewritten as TypeScript and a `tsconfig.json` referenced from the root,
so `npm run typecheck` and `npm run lint:js` cover it. Its behavior is
unchanged: on the same report, it prints the same annotations, writes the
same job summary, and exits with the same status.

Node runs the script by stripping the types, as with `tools/pr-meta`, so
the PHPStan job now sets up the Node version in `.nvmrc` before the
report step, rather than relying on the runner's default. The script
imports nothing outside Node, so the job still installs no npm
dependencies.

The CODEOWNERS entry for the old path is removed, since the existing
`/tools/phpstan` entry now covers the script.

Co-Authored-By: Claude Opus 5.5 (high effort) <noreply@anthropic.com>
Claude-Code-Session-Id: 0e3bd725-76b2-4ebf-9478-771dc675b691
@github-actions

github-actions Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

🤖 PR meta 🤖

Preview in WordPress Playground Preview in Storybook

🎉 Props

If you're merging code through a pull request on GitHub, copy and paste the following into the bottom of the merge commit message.

Co-authored-by: westonruter <westonruter@git.wordpress.org>
Co-authored-by: manzoorwanijk <manzoorwanijk@git.wordpress.org>

To understand the WordPress project's expectations around crediting contributors, please review the Contributor Attribution page in the Core Handbook.


Props Bot commands

You can trigger Props Bot actions by commenting on this PR:

  • @propsbot add <username> to give props to a GitHub user. Requires write access.
  • @propsbot remove <username> to leave a user out of the props. Requires write access.
  • @propsbot refresh to refresh this list. Adding the props-bot label works too.

Updated as activity occurs, without notifying anyone named here. Add the props-bot label to refresh.

📦 Bundle size

Size Change: 0 B

Total Size: 8.58 MB

42df232 Run

⚡ Performance

Show the results

Client side metrics exclude the server response time.

front-end-block-theme

Metric 901516f trunk % Change
timeToFirstByte 57.55 ms +4.17% -5.65% 56.65 ms +4.94% -2.65% 1.59%
largestContentfulPaint 92 ms +8.7% -4.35% 92 ms +8.7% -4.35% 0%
lcpMinusTtfb 34.55 ms +5.93% -0.87% 35.25 ms +11.77% -8.51% -1.99%
wpBeforeTemplate 29.81 ms +8.59% -3.52% 29.45 ms +2.72% -2.34% 1.22%
wpTemplate 22.28 ms +8.21% -2.92% 23.02 ms +3.04% -5.13% -3.21%
wpTotal 53.47 ms +4.79% -4.99% 52.75 ms +5.12% -2.82% 1.36%
wpMemoryUsage 7.71 MB +0% -0% 7.67 MB +0% -0% 0.43%
wpDbQueries 16 +0% -0% 16 +0% -0% 0%

front-end-classic-theme

Metric 901516f trunk % Change
timeToFirstByte 48 ms +8.44% -2.81% 47.55 ms +5.15% -1.89% 0.95%
largestContentfulPaint 104 ms +3.85% -3.85% 100 ms +4% -0% 4%
lcpMinusTtfb 54.9 ms +2.37% -2.73% 53.65 ms +2.42% -0.84% 2.33%
wpBeforeTemplate 27.61 ms +10.18% -4.42% 27.32 ms +8.86% -2.45% 1.06%
wpTemplate 17.45 ms +4.24% -3.32% 16.9 ms +4.26% -2.9% 3.25%
wpTotal 44.85 ms +8.27% -2.88% 44.39 ms +5% -2.23% 1.04%
wpMemoryUsage 6.40 MB +0% -0% 6.36 MB +0% -0% 0.53%
wpDbQueries 13 +0% -0% 13 +0% -0% 0%

media-processing

Metric 901516f trunk % Change
mediaProcessingJpeg 408.55 ms +0.72% -1.38% 403.68 ms +1.74% -0.17% 1.21%
mediaProcessingAvif 6156.22 ms +0.51% -0.28% 6174.7 ms +0.3% -0.45% -0.3%
mediaProcessingJpegToAvif 4277.06 ms +0.34% -0.53% 4246.6 ms +0.49% -0.18% 0.72%

media-upload

Metric 901516f trunk % Change
jpegUploadProcessing 1700.84 ms +16.43% -14.6% 1460.77 ms +7.09% -0.71% 16.43%
pngUploadProcessing 212.99 ms +31.31% -18.73% 169.71 ms +7.08% -6.77% 25.5%
largeJpegUploadProcessing 1701.29 ms +14.33% -14.78% 1466.93 ms +32.28% -0.55% 15.98%
multipleImageUploadProcessing 2030.35 ms +1.61% -0.46% 2004.08 ms +2.24% -0.3% 1.31%

post-editor

Metric 901516f trunk % Change
serverResponse 327.43 ms +2.16% -12.74% 304.81 ms +8.31% -6.83% 7.42%
firstPaint 281.75 ms +37.52% -8.3% 311.21 ms +22.16% -23.83% -9.47%
domContentLoaded 670.4 ms +2.89% -0.72% 660.14 ms +2.56% -0.51% 1.55%
loaded 671.15 ms +2.88% -0.73% 660.96 ms +2.54% -0.53% 1.54%
firstContentfulPaint 394.6 ms +0.85% -1.59% 384.6 ms +3.7% -1.44% 2.6%
firstBlock 1703.51 ms +1.58% -0.62% 1676.37 ms +0.96% -0.45% 1.62%
type 14.61 ms +2.87% -7.32% 13.87 ms +8.44% -1.73% 5.34%
typeWithoutInspector 15.31 ms +4.64% -9.34% 14.06 ms +2.56% -7.61% 8.89%
typeWithTopToolbar 19.85 ms +3.22% -9.07% 18.7 ms +5.24% -5.72% 6.15%
typeContainer 6.76 ms +12.57% -8.28% 6.54 ms +14.07% -2.75% 3.36%
focus 98.24 ms +3.7% -2.9% 87.37 ms +6.02% -1.11% 12.44%
firstFocus 215.62 ms +0% -0% 183.75 ms +0% -0% 17.34%
selectAll 322.96 ms +3.79% -4.02% 295.72 ms +1.46% -0.52% 9.21%
undo 25.05 ms +4.83% -6.15% 24.57 ms +8.02% -2.69% 1.95%
listViewOpen 59.79 ms +12.69% -3.58% 57.11 ms +8.97% -11.77% 4.69%
inserterOpen 35.33 ms +3.23% -6.88% 32.78 ms +4.24% -4.82% 7.78%
inserterHover 9.39 ms +3.19% -5.75% 8.77 ms +4.68% -5.25% 7.07%
inserterSearch 7.19 ms +7.51% -5.42% 7.09 ms +5.5% -5.64% 1.41%
loadPatterns 496.68 ms +6.26% -0.35% 494.55 ms +1.52% -2.57% 0.43%
wpTotal 318.33 ms +1.92% -12.98% 296.38 ms +8.16% -7.08% 7.41%
wpMemoryUsage 14.75 MB +0% -0% 14.72 MB +0% -0% 0.24%
wpDbQueries 55 +0% -0% 55 +0% -0% 0%

site-editor

Metric 901516f trunk % Change
serverResponse 476.84 ms +0.27% -5.47% 461.48 ms +4.69% -4.94% 3.33%
firstPaint 364.88 ms +2.29% -6.47% 362.21 ms +8.38% -5.33% 0.74%
domContentLoaded 873.63 ms +4.75% -2.6% 865.45 ms +1.59% -1.65% 0.95%
loaded 874.63 ms +4.76% -2.59% 866.55 ms +1.6% -1.65% 0.93%
firstContentfulPaint 498.67 ms +8.89% -1.21% 500.85 ms +2.11% -0.18% -0.44%
firstBlock 2549.81 ms +6.21% -0.67% 2538.6 ms +0.09% -0.88% 0.44%
type 17.84 ms +14.52% -6.05% 17.67 ms +15.22% -8.55% 0.96%
navigate 116.27 ms +7.32% -3.95% 114.04 ms +11.44% -0.92% 1.96%
loadPatterns 1281.86 ms +9.99% -13.38% 1328.14 ms +8.99% -9.82% -3.48%
loadPages 1018.39 ms +6.19% -3.76% 1005.97 ms +3.47% -2.75% 1.23%
wpTotal 467.36 ms +0.27% -5.59% 452.08 ms +4.79% -5.07% 3.38%
wpMemoryUsage 12.77 MB +0% -0% 12.73 MB +0% -0% 0.27%
wpDbQueries 45 +0% -0% 45 +0% -0% 0%

42df232 Run

🏁 Flaky tests

Some tests passed with failed attempts. The failures may not be related to this commit but are still reported for visibility. See the documentation for more information.

Undo restores the post selection when another synced entity is loaded (RTC WebSocket) in /test/e2e/specs/editor/collaboration/collaboration-undo-redo.spec.ts, passed after 1 failed attempt.
Error: expect(received).toMatchObject(expected)

- Expected  - 2
+ Received  + 2

  Object {
    "attributeKey": "content",
    "blockIndex": 0,
    "content": "abcdef",
-   "endOffset": 3,
-   "offset": 3,
+   "endOffset": 1,
+   "offset": 1,
  }

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/collaboration/collaboration-undo-redo.spec.ts:363:5
a Core-loading-bar equivalent with a consumer-side 400 ms debounce in /test/e2e/specs/interactivity/router-navigation-lifecycle.spec.ts, passed after 1 failed attempt.
Error: apiRequestContext.fetch: socket hang up
Call log:
  - → DELETE http://localhost:8891/wp-json/wp/v2/posts/307?force=true
    - user-agent: Playwright/1.63.0 (x64; ubuntu 24.04) node/24.20 CI/1
    - accept: */*
    - accept-encoding: gzip,deflate,br
    - X-WP-Nonce: ca845fbff5
    - cookie: wordpress_test_cookie=WP%20Cookie%20check; wordpress_logged_in_da433b7faafd8180ebeb1624ceeeab7e=admin%7C1793849378%7C3BjsSAwKHGx2fE9jdvPA0BlEoHEfiD3U8xyMRYOdpdK%7C1c8424c68a577967c4cf68a6981366e9b741235932bef4f05ac15d4b5090d282; wp-settings-time-1=1793677002

    at RequestUtils.rest (/home/runner/work/gutenberg/gutenberg/packages/e2e-test-utils-playwright/src/request-utils/rest.ts:111:39)
    at map (/home/runner/work/gutenberg/gutenberg/packages/e2e-test-utils-playwright/src/request-utils/posts.ts:44:9)
    at RequestUtils.deleteAllPosts (/home/runner/work/gutenberg/gutenberg/packages/e2e-test-utils-playwright/src/request-utils/posts.ts:43:9)
    at InteractivityUtils.deleteAllPosts (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/interactivity/fixtures/interactivity-utils.ts:91:3)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/interactivity/router-navigation-lifecycle.spec.ts:384:3
does not select blocks in the canvas preview when editing from the sidebar (Site Editor v2) in /test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js, passed after 1 failed attempt.
Error: expect(locator).toBeFocused() failed

Locator: getByRole('combobox', { name: 'Search or type URL' })
Expected: focused
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toBeFocused" getByRole('combobox', { name: 'Search or type URL' }) with timeout 5000ms
  - waiting for getByRole('combobox', { name: 'Search or type URL' })

    at LinkControl.useLinkControlSearch (/home/runner/work/gutenberg/gutenberg/test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js:416:47)
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/site-editor/navigation-sidebar-list-view.spec.js:179:21
keeps the inline inserter open when the Block Library panel closes in /test/e2e/specs/editor/various/inserting-blocks.spec.js, passed after 1 failed attempt.
TimeoutError: locator.click: Timeout 10000ms exceeded.
Call log:
  - waiting for getByRole('button', { name: 'Add block' })

    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/inserting-blocks.spec.js:614:59
should restore an older revision in /test/e2e/specs/editor/various/revisions.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

Expected: true
Received: false

Call Log:
- Timeout 5000ms exceeded while waiting on the predicate
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/revisions.spec.js:120:45
should show format toolbar when selecting text from the left edge of a block in /test/e2e/specs/editor/various/writing-flow.spec.js, passed after 1 failed attempt.
Error: expect(locator).toBeVisible() failed

Locator: getByRole('toolbar', { name: 'Block tools' }).getByRole('button', { name: 'Bold' })
Expected: visible
Timeout: 5000ms
Error: element(s) not found

Call log:
  - Expect "toBeVisible" getByRole('toolbar', { name: 'Block tools' }).getByRole('button', { name: 'Bold' }) with timeout 5000ms
  - waiting for getByRole('toolbar', { name: 'Block tools' }).getByRole('button', { name: 'Bold' })

    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/various/writing-flow.spec.js:1532:5
should undo without broken temporary state in /test/e2e/specs/editor/blocks/image.spec.js, passed after 1 failed attempt.
Error: expect(received).toBe(expected) // Object.is equality

- Expected  - 2
+ Received  + 2

- <!-- wp:image -->
- <figure class="wp-block-image"><img alt=""/></figure>
+ <!-- wp:image {"id":24,"sizeSlug":"full","linkDestination":"none"} -->
+ <figure class="wp-block-image size-full"><img src="http://localhost:8891/wp-content/uploads/2026/11/1b41b33d-f26f-4499-baf8-2e9eb53d8908.png" alt="" class="wp-image-24"/></figure>
  <!-- /wp:image -->
    at /home/runner/work/gutenberg/gutenberg/test/e2e/specs/editor/blocks/image.spec.js:432:49

42df232 Run

@westonruter westonruter added the [Type] Build Tooling Issues or PRs related to build tooling label Nov 3, 2026
@westonruter
westonruter marked this pull request as ready for review November 3, 2026 02:31
@westonruter
westonruter requested review from manzoorwanijk and removed request for desrosj, fabiankaegy and juanmaguitar November 3, 2026 02:32
@westonruter westonruter added the No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core label Nov 3, 2026
Comment thread .github/workflows/unit-test.yml Outdated
# the pull request did not change and those with no line, such as an out-of-date baseline.
- name: Report the PHPStan results
run: node .github/workflows/scripts/phpstan-report.js "${RUNNER_TEMP}/phpstan-report.json"
run: node tools/phpstan/report.ts "${RUNNER_TEMP}/phpstan-report.json"

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Instead of invoking it from root, let us create a script in the workspace and then use npm run —workspace … syntax here like we do for other tools.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

🤖 Comment by Claude Opus 5.5 · high effort

Done in 42df232: tools/phpstan/package.json now has a report script, and the job runs npm run --workspace @wordpress/phpstan-tools report -- "${RUNNER_TEMP}/phpstan-report.json".

Since npm run requires the npm version in the devEngines of the root package.json, the job now also runs ./.github/setup-npm (with cache: false) after setting up Node, as static-checks.yml does. No npm dependencies are installed, since npm run --workspace only reads the package.json files.

As npm run runs the script in tools/phpstan, the file paths in the report are made relative to the repository root, found from the script's own location when GITHUB_WORKSPACE is not set, and a relative path to the report is resolved from INIT_CWD, so it can also be run locally from the root. The output in CI is unchanged.

CI now runs the report with `npm run --workspace @wordpress/phpstan-tools
report`, as it does for the other tools, rather than invoking the file
from the repository root.

`npm run` requires the npm version in the `devEngines` of package.json,
so the job sets it up with `./.github/setup-npm` after Node. The
dependencies are still not installed, since `npm run --workspace` only
reads the package.json files.

`npm run` also runs the script in the workspace directory. The paths in
the report are therefore made relative to the repository root, found
from the script's own location when `GITHUB_WORKSPACE` is not set, and a
relative path to the report is resolved from `INIT_CWD`, the directory
that npm was run in. In CI, the report path is absolute and
`GITHUB_WORKSPACE` is set, so the output is unchanged.

Co-Authored-By: Claude Opus 5.5 (high effort) <noreply@anthropic.com>
Claude-Code-Session-Id: 0e3bd725-76b2-4ebf-9478-771dc675b691
@westonruter
westonruter merged commit 39836cd into trunk Nov 3, 2026
106 of 109 checks passed
@westonruter
westonruter deleted the tools/phpstan-report-workspace branch November 3, 2026 05:59
@github-actions github-actions Bot added this to the Gutenberg 24.4 milestone Nov 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

No Core Sync Required Indicates that any changes do not need to be synced to WordPress Core [Type] Build Tooling Issues or PRs related to build tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants