|
| 1 | +name: "Claude Update Error Docs" |
| 2 | + |
| 3 | +on: |
| 4 | + workflow_dispatch: |
| 5 | + inputs: |
| 6 | + sha: |
| 7 | + description: "phpstan/phpstan commit with errorIdentifiers.json change" |
| 8 | + required: true |
| 9 | + type: string |
| 10 | + workflow_call: |
| 11 | + inputs: |
| 12 | + sha: |
| 13 | + description: "phpstan/phpstan commit with errorIdentifiers.json change" |
| 14 | + required: true |
| 15 | + type: string |
| 16 | + |
| 17 | +permissions: |
| 18 | + contents: read |
| 19 | + |
| 20 | +jobs: |
| 21 | + fix: |
| 22 | + name: "Update Error Docs" |
| 23 | + runs-on: "ubuntu-latest" |
| 24 | + timeout-minutes: 60 |
| 25 | + permissions: |
| 26 | + contents: read |
| 27 | + issues: read |
| 28 | + pull-requests: write |
| 29 | + |
| 30 | + steps: |
| 31 | + - name: Harden the runner (Audit all outbound calls) |
| 32 | + uses: step-security/harden-runner@5ef0c079ce82195b2a36a210272d6b661572d83e # v2.14.2 |
| 33 | + with: |
| 34 | + egress-policy: audit |
| 35 | + |
| 36 | + - name: "Checkout" |
| 37 | + uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4.3.1 |
| 38 | + with: |
| 39 | + ref: ${{ inputs.issue-number }} |
| 40 | + repository: phpstan/phpstan |
| 41 | + fetch-depth: 10 |
| 42 | + |
| 43 | + - name: "Install Claude Code" |
| 44 | + run: npm install -g @anthropic-ai/claude-code |
| 45 | + |
| 46 | + - name: "Run Claude Code" |
| 47 | + env: |
| 48 | + CLAUDE_CODE_OAUTH_TOKEN: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} |
| 49 | + GH_TOKEN: ${{ secrets.PHPSTAN_BOT_FORK_TOKEN }} |
| 50 | + run: | |
| 51 | + git config user.name "phpstan-bot" |
| 52 | + git config user.email "ondrej+phpstanbot@mirtes.cz" |
| 53 | +
|
| 54 | + claude --model claude-opus-4-6 \ |
| 55 | + --dangerously-skip-permissions \ |
| 56 | + -p "$(cat << 'PROMPT_EOF' |
| 57 | + You are generating documentation pages for PHPStan error identifiers. PHPStan is a PHP static analysis tool that finds bugs in code without running it. Each error identifier (like `argument.type`, `deadCode.unreachable`, `property.notFound`) categorizes a specific type of error. |
| 58 | + |
| 59 | + The goal is to create a markdown file for each identifier in `website/errors/` explaining what the error means, showing a code example, and offering ways to fix it. |
| 60 | + |
| 61 | + ## Step 1: Find what changed |
| 62 | + |
| 63 | + See the checked out commit of the current repository. It should contain changes to `website/src/errorsIdentifiers.json`. See if the diff means any identifier was added or updated. |
| 64 | + |
| 65 | + Read `website/src/errorsIdentifiers.json`. This JSON maps each identifier to its rule classes and source code locations: |
| 66 | + |
| 67 | + ```json |
| 68 | + { |
| 69 | + "argument.type": { |
| 70 | + "PHPStan\\Rules\\Functions\\CallToFunctionParametersRule": { |
| 71 | + "phpstan/phpstan-src": [ |
| 72 | + "https://github.com/phpstan/phpstan-src/blob/2.2.x/src/Rules/FunctionCallParametersCheck.php#L280" |
| 73 | + ] |
| 74 | + } |
| 75 | + } |
| 76 | + } |
| 77 | + ``` |
| 78 | + |
| 79 | + Then list existing files in `website/errors/`. Each file is named `<identifier>.md`. |
| 80 | + |
| 81 | + You will document any identifiers that do not have .md file. You will also look if any identifiers involved in the diff of the checked out commit need updating the docs. |
| 82 | + |
| 83 | + ## Step 2: Clone required repositories |
| 84 | + |
| 85 | + Clone only the repositories referenced by the affected identifiers. Extract the branch name from the GitHub URLs (e.g., `blob/2.2.x/` → branch `2.2.x`). |
| 86 | + |
| 87 | + Use shallow clones to save time: |
| 88 | + |
| 89 | + ```bash |
| 90 | + git clone --depth 1 --branch <branch> https://github.com/phpstan/<repo>.git /tmp/repos/<repo> |
| 91 | + ``` |
| 92 | + |
| 93 | + The possible repositories are: |
| 94 | + - `phpstan/phpstan-src` (typically branch `2.2.x`) |
| 95 | + - `phpstan/phpstan-strict-rules` |
| 96 | + - `phpstan/phpstan-deprecation-rules` |
| 97 | + - `phpstan/phpstan-doctrine` |
| 98 | + - `phpstan/phpstan-symfony` |
| 99 | + - `phpstan/phpstan-phpunit` |
| 100 | + - `phpstan/phpstan-nette` |
| 101 | + |
| 102 | + ## Step 3: Research each identifier |
| 103 | + |
| 104 | + For each identifier, gather the information needed to write the documentation. |
| 105 | + |
| 106 | + ### 3a. Read the rule source code |
| 107 | + |
| 108 | + From the JSON URLs, extract the file path and line number. Read the source code around those lines to find: |
| 109 | + |
| 110 | + 1. **Error message**: Look for `RuleErrorBuilder::message('...')` — this is the exact error text PHPStan shows |
| 111 | + 2. **Trigger condition**: Read the surrounding `processNode()` method to understand what code pattern causes this error |
| 112 | + 3. **Tips**: Look for `->tip('...')` or `->addTip('...')` calls in the same builder chain — these often contain links to blog posts or documentation pages on phpstan.org |
| 113 | + 4. **Non-ignorable**: Check for `->nonIgnorable()` in the builder chain |
| 114 | + |
| 115 | + ### 3b. Understand the identifier prefix when source uses `$location->createIdentifier()` |
| 116 | + |
| 117 | + When reading the rule source code in step 3a, check whether the linked source code line uses `$location->createIdentifier()`. If it does, the identifier prefix comes from `ClassNameUsageLocation` in phpstan-src, and the prefix indicates a specific PHP language feature — which may not be obvious from the prefix name alone. |
| 118 | + |
| 119 | + If the source code does **not** use `$location->createIdentifier()`, the prefix is set directly by the rule and typically describes its PHP feature straightforwardly. |
| 120 | + |
| 121 | + Consult the "Identifier prefix reference" section in `website/errors/CLAUDE.md` for the complete prefix-to-PHP-feature mapping tables. |
| 122 | + |
| 123 | + ### 3c. Find test fixtures with code examples |
| 124 | + |
| 125 | + For a rule class like `PHPStan\Rules\Functions\CallToFunctionParametersRule`: |
| 126 | + - Test class: `tests/PHPStan/Rules/Functions/CallToFunctionParametersRuleTest.php` |
| 127 | + - Test data: `tests/PHPStan/Rules/Functions/data/*.php` |
| 128 | + |
| 129 | + For extension repos (phpstan-doctrine, phpstan-symfony, etc.), the path pattern may differ — check `tests/Rules/` or `tests/` directories. |
| 130 | + |
| 131 | + Read the test class to find which data files trigger this specific identifier. Look for the error message text in the test assertions: |
| 132 | + |
| 133 | + ```php |
| 134 | + $this->analyse([__DIR__ . '/data/someFile.php'], [ |
| 135 | + ['Error message text', 42], |
| 136 | + ]); |
| 137 | + ``` |
| 138 | + |
| 139 | + Then read the corresponding data file to extract a minimal code example. |
| 140 | + |
| 141 | + ### 3d. Determine if the error is ignorable |
| 142 | + |
| 143 | + An error identifier is **not ignorable** if: |
| 144 | + - The source code uses `->nonIgnorable()` in the error builder chain |
| 145 | + - The identifier starts with `phpstan.` (internal PHPStan errors) |
| 146 | + - The identifier starts with `phpstanPlayground.` (playground-specific) |
| 147 | + |
| 148 | + All other identifiers are ignorable. |
| 149 | + |
| 150 | + ### 3e. Check for configuration options |
| 151 | + |
| 152 | + Some rules accept constructor parameters from PHPStan configuration. Look at the rule class constructor for injected config values. Cross-reference with `website/src/config-reference.md` to find the documented parameter name. |
| 153 | + |
| 154 | + Examples of configurable rules: |
| 155 | + - Rules that check strict types may be controlled by `treatPhpDocTypesAsCertain` |
| 156 | + - Dead code rules may be controlled by `checkAlwaysTrueCheckTypeFunctionCall` |
| 157 | + - Some rules are only active at certain PHPStan levels |
| 158 | + |
| 159 | + ## Step 4: Generate markdown files |
| 160 | + |
| 161 | + Create `website/errors/` directory if it doesn't exist. |
| 162 | + |
| 163 | + For each identifier, create `website/errors/<identifier>.md` following the file format, content guidelines, and tone described in `website/errors/CLAUDE.md`. Read that file before generating any markdown. |
| 164 | +
|
| 165 | + ## Step 5: Write a summary |
| 166 | +
|
| 167 | + After completing the fix, write two files: |
| 168 | +
|
| 169 | + 1. /tmp/commit-message.txt - A concise commit message (first line: short summary under 72 chars, then a blank line, then a few bullet points describing key changes). Example: |
| 170 | + Update error docs - added new `new.trait` identifier |
| 171 | + |
| 172 | + More detailed description of the commit |
| 173 | + 2. /tmp/pr-description.md - A pull request description in this format: |
| 174 | + What was the work involved in updating the docs. |
| 175 | +
|
| 176 | + These files are critical - they will be used for the commit message and PR description. |
| 177 | + |
| 178 | + PROMPT_EOF |
| 179 | + )" |
| 180 | +
|
| 181 | + - name: "Read Claude's summary" |
| 182 | + id: claude-summary |
| 183 | + env: |
| 184 | + ISSUE_NUMBER: ${{ inputs.issue-number }} |
| 185 | + run: | |
| 186 | + if [ -f /tmp/commit-message.txt ]; then |
| 187 | + delimiter="EOF_$(openssl rand -hex 16)" |
| 188 | + { |
| 189 | + echo "commit_message<<${delimiter}" |
| 190 | + cat /tmp/commit-message.txt |
| 191 | + echo "${delimiter}" |
| 192 | + } >> "$GITHUB_OUTPUT" |
| 193 | + else |
| 194 | + echo "commit_message=Update error identifiers docs" >> "$GITHUB_OUTPUT" |
| 195 | + fi |
| 196 | +
|
| 197 | + if [ -f /tmp/pr-description.md ]; then |
| 198 | + delimiter="EOF_$(openssl rand -hex 16)" |
| 199 | + { |
| 200 | + echo "pr_body<<${delimiter}" |
| 201 | + cat /tmp/pr-description.md |
| 202 | + echo "${delimiter}" |
| 203 | + } >> "$GITHUB_OUTPUT" |
| 204 | + else |
| 205 | + echo "pr_bodyUpdate error identifiers docs" >> "$GITHUB_OUTPUT" |
| 206 | + fi |
| 207 | +
|
| 208 | + - name: "Create Pull Request" |
| 209 | + id: create-pr |
| 210 | + uses: peter-evans/create-pull-request@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 |
| 211 | + with: |
| 212 | + branch-token: ${{ secrets.PHPSTAN_BOT_FORK_TOKEN }} |
| 213 | + token: ${{ secrets.PHPSTAN_BOT_PR_TOKEN }} |
| 214 | + push-to-fork: phpstan-bot/phpstan |
| 215 | + branch-suffix: random |
| 216 | + delete-branch: true |
| 217 | + title: "Update Error Docs" |
| 218 | + body: ${{ steps.claude-summary.outputs.pr_body }} |
| 219 | + committer: "phpstan-bot <ondrej+phpstanbot@mirtes.cz>" |
| 220 | + commit-message: ${{ steps.claude-summary.outputs.commit_message }} |
0 commit comments