As I was looking for an alternative to PHPStan that’s a touch faster, SemGrep – a lightweight static analysis software appeared in the searches, and I was mostly excited about the fact that it supports multiple languages, including PHP, JavaScript, JSX, JSON, TypeScript… and since nowadays Python rises to the top most often, the fact that it ticked that box too was a nice to have.
Steps for adding SemGrep validation to a WordPress project that already-uses pre-commit hooks with npm exec lint-staged (link to the package on NPMjs: lint-staged)
Add a new lint-semgrep. Without the --error flag the commit is not blocked when rules are broken.
package.json
...
"scripts": {
...
"lint-semgrep": "docker run --rm -v $PWD:/src -w /src semgrep/semgrep semgrep --error --config .semgrep/rules.yml",
.lintstagedrc.js
const path = require('path');
// Convert absolute paths to relative paths for Docker compatibility.
const toRelative = (files) => files.map((f) => path.relative(process.cwd(), f)).join(' ');
module.exports = {
// Use a callback to avoid passing the changed file paths to the script.
'composer.*': () => 'npm run lint-composer',
// lint-php runs on all files; semgrep receives staged file paths (relative for Docker).
'*.php': (files) => ['npm run lint-php', `npm run lint-semgrep -- ${toRelative(files)}`],
'*.json': (files) => `npm run lint-semgrep -- ${toRelative(files)}`,
}
Example of a good first rule – futureproofing for Collaborative Editing, inspired by what was raised in the Real-time collaboration: Early user feedback post, particularly in the Where things go wrong section:
.semgrep/rules.yml
rules:
- id: wp-meta-missing-rest-arg
message: |
Post meta registered without 'show_in_rest' => true will break Real-Time Collaboration.
Add 'show_in_rest' => true to the arguments array.
languages: [php]
severity: ERROR
patterns:
- pattern-either:
- pattern: register_post_meta($TYPE, $KEY, $ARGS)
- pattern: register_meta($TYPE, $KEY, $ARGS)
- pattern-not: register_post_meta($TYPE, $KEY, [..., "show_in_rest" => true, ...])
- pattern-not: register_post_meta($TYPE, $KEY, [..., 'show_in_rest' => true, ...])
- pattern-not: register_meta($TYPE, $KEY, [..., "show_in_rest" => true, ...])
- pattern-not: register_meta($TYPE, $KEY, [..., 'show_in_rest' => true, ...])
- id: block-json-uses-meta
message: |
Block uses 'source': 'meta'. Ensure the corresponding PHP meta registration has 'show_in_rest' => true.
languages: [json]
severity: WARNING
pattern: |
"source": "meta"

