ci(rows): a level-9 ruler for the classes that read rows from $wpdb (#1060) - #1071
Merged
Conversation
…1060) $wpdb returns every column as a string, so a row is really array<string, string|null>. Classes that declare it as array<string, mixed> hand PHPStan nothing to check: passing mixed into a typed parameter is a level-9 check and the main gate runs at level 8. That is how a fatal TypeError reached production through a green CI (#1058), and it is invisible by construction rather than an oversight in any one class. phpstan-rows.neon.dist analyses the same tree at level 9 and .github/scripts/phpstan-rows-report.php narrows the output to the files that read rows: 243 errors today, against 2948 for the repository as a whole. The tree is deliberately NOT restricted to those files, because the obvious configuration measures the wrong thing. Narrowing paths to the 45 readers takes the rest of includes/ out of the analysis, so PHPStan loses the inferred return types of everything they call and invents mixed that does not exist: 778 errors, or 363 with scanDirectories, against a true 243. Chasing that gap would mean annotating things that are not defects. The file list is discovered by scanning, never committed, so the 46th reader is in scope the day it is written. A malformed or missing report exits 2 rather than reading as zero errors — a gate that silently passes is the same defect class one level up, and PhpstanRowsReportTest pins that along with the threshold and the exclusion of errors outside the readers. The CI job is non-blocking for now, with the allowance ratcheting down as each PR of the issue lands and the flag coming off at zero. Same evidence-gated shape as the post-deploy smoke: read the step output, not the job conclusion, while the flag is on. No production code changes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
Coverage Report for CI Build 34001596359Warning No base build found for commit Coverage: 89.899%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsRequires a base build to compare against. How to fix this → Coverage Stats
💛 - Coveralls |
rpgmem
marked this pull request as ready for review
September 6, 2026 01:09
This was referenced Sep 6, 2026
rpgmem
added a commit
that referenced
this pull request
Sep 6, 2026
… passo 2) (#1089) Nada impedia que o 40o absint( $_POST['x'] ) entrasse amanha — sem isso o passo 1 desanda sozinho. RequestInputCastTest congela a populacao em baseline, no mesmo molde dos outros cinco guardas do repositorio (ModuleBoundary, AjaxWiring, SettingsDefaults, os dois de supressao, a catraca de teste vazio): catraca que so encolhe, regeneracao por variavel de ambiente, diff revisado. 55 entradas em 21 arquivos, nenhuma em views/. Verificado nos dois sentidos: um cast novo falha nomeando o site e apontando o acessor; um cast removido tambem falha, mandando apertar a baseline. Sem allowlist, e isso e medido, nao suposto: os 55 sao absint(). Zero intval, zero (int) sobre superglobal. Como get_post_int()/get_get_int() sao construidos sobre absint(), servem para todos — nao ha caso de inteiro com sinal que peca excecao. Se um dia aparecer, o remedio e um acessor novo, nao uma excecao aqui. A entrada e arquivo::SUPERGLOBAL[chave]#n, nao linha: linha muda a cada refactor e o repositorio ja evita cita-las, enquanto o ordinal por par (arquivo, chave) e estavel sob movimentacao e ainda faz uma segunda leitura identica no mesmo arquivo virar entrada propria. Um segundo teste protege o proprio scanner: se a varredura devolver vazio, ele falha. Sem isso, no dia em que o regex ou o caminho quebrar, a catraca passaria em silencio com zero entradas — a mesma classe de defeito que o #1071 resolveu no relatorio da regua. Os dois limites ficam declarados no docblock: o guarda so ve a forma adjacente ao cast (seguir o valor pediria AST) e ve presenca, nunca correcao. Refs #1087, #1075, #1079 Claude-Session: https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU Co-authored-by: Claude <noreply@anthropic.com>
This was referenced Sep 7, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR 1 de 6 da #1060. Nenhuma mudança em código de produção — só a régua, sem a qual as PRs seguintes não têm como provar que reduziram alguma coisa.
phpstan-rows.neon.dist— nível 9 sobre a mesma árvore do gate principal..github/scripts/phpstan-rows-report.php— estreita o relatório para os arquivos que leem linhas: 243 erros, contra 2948 do repositório inteiro.PhpstanRowsReportTest— 5 casos.Por que a árvore não é restrita aos 45 arquivos
A configuração óbvia mede a coisa errada, e eu medi as três antes de escolher:
paths: includes/(referência)paths:só os 45 arquivosscanDirectories: includes/Restringir
pathstira o resto deincludes/da análise, então o PHPStan perde os tipos de retorno inferidos de tudo que essas classes chamam e inventamixedque não existe. Metade dos 778 é artefato da configuração; persegui-los significaria anotar coisas que não são defeito.scanDirectoriesdá símbolos, não inferência.A lista de arquivos é descoberta, não commitada
O script varre
includes/procurando$wpdb->get_row|get_results|get_col. A 46ª classe que ler linhas entra no escopo no dia em que for escrita — que é exatamente a forma como uma lista mantida à mão falha.O gate falha alto, não em silêncio
Relatório ausente, truncado ou com formato mudado → exit 2, não "zero erros". Um gate que passa em silêncio quando a medição quebrou é a mesma classe de defeito da issue, um nível acima. O teste cobre isso, mais a folga e a exclusão dos erros fora dos leitores.
O guard proposto na issue deixou de ser necessário
O corpo da #1060 previa um
RowShapeCoverageTestconferindo presença de@phpstan-type, com anotação de "não se aplica". Com o gate de nível 9 isso some: arquivo sem erro passa, sem fixture para manter e sem estado ambíguo. ODatabaseHelperTrait— que a issue colocava no Tier 2 por ser trait — tem zero erros: lêSHOW COLUMNS/SHOW INDEX, que não são registros de domínio, e já casta todo acesso. IdemActivityLogeRateLimitRepository. 23 das 45 classes já estão em zero.Por que não-bloqueante
243 erros hoje; um job vermelho em toda PR ensina as pessoas a ignorá-lo. Mesma forma do smoke pós-deploy: roda com
continue-on-errorenquanto a folga desce, e o flag sai em zero na PR 6. Enquanto estiver ligado, o número está na saída do passo, não na conclusão do job.Type of change
Test plan
vendor/bin/phpunit— 7598 testes, 21803 asserções, verdecomposer lint(PHPStan nível 8) — verde, rodado localmente desta vez (PHAR 2.2.10, a versão do lock; ocomposer installnão autentica neste container, o PHAR baixa pelos releases)phpcs-tests.xml.distlimposChecklist
CHANGELOG.mdatualizado ([Unreleased] / Added)readme.txt— não se aplica: a versão só é consolidada na PR de releasephpstan-rows.neon.distexcluído do zip (.distignore) e do deploy para o testesNúmeros e passos mapeados em #1060 (comment).
🤖 Generated with Claude Code
https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU
Generated by Claude Code