Skip to content

ci(rows): a level-9 ruler for the classes that read rows from $wpdb (#1060) - #1071

Merged
rpgmem merged 1 commit into
developfrom
claude/row-shapes-ruler-4vvuer
Sep 6, 2026
Merged

ci(rows): a level-9 ruler for the classes that read rows from $wpdb (#1060)#1071
rpgmem merged 1 commit into
developfrom
claude/row-shapes-ruler-4vvuer

Conversation

@rpgmem

@rpgmem rpgmem commented Sep 6, 2026

Copy link
Copy Markdown
Owner

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.
  • Job de CI não-bloqueante, com folga 243 que baixa a cada PR da issue.
  • 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:

configuração erros
paths: includes/ (referência) 243
paths: só os 45 arquivos 778
idem + scanDirectories: includes/ 363

Restringir paths tira o resto de includes/ da análise, então o PHPStan perde os tipos de retorno inferidos de tudo que essas classes chamam e inventa mixed que não existe. Metade dos 778 é artefato da configuração; persegui-los significaria anotar coisas que não são defeito. scanDirectories dá 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 RowShapeCoverageTest conferindo 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. O DatabaseHelperTrait — 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. Idem ActivityLog e RateLimitRepository. 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-error enquanto 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

  • Bug fix (non-breaking change that fixes an issue)
  • New feature (non-breaking change that adds functionality)
  • Breaking change (fix or feature that would alter existing behavior)
  • Refactor / chore (no functional change)
  • Documentation only

Test plan

  • vendor/bin/phpunit — 7598 testes, 21803 asserções, verde
  • composer lint (PHPStan nível 8) — verde, rodado localmente desta vez (PHAR 2.2.10, a versão do lock; o composer install não autentica neste container, o PHAR baixa pelos releases)
  • Script novo verificado contra o relatório real: reproduz 243 exatamente
  • Run a frio do nível 9 cronometrado: 59s — viável como job de CI
  • WPCS e phpcs-tests.xml.dist limpos
  • Smoke manual — não se aplica, nada de produção mudou

Checklist

  • Sem mudanças em CSS/JS
  • CHANGELOG.md atualizado ([Unreleased] / Added)
  • readme.txt — não se aplica: a versão só é consolidada na PR de release
  • Sem novas entradas na baseline do PHPStan (não existe baseline neste repo)
  • Sem segredos, tokens ou PII no diff
  • phpstan-rows.neon.dist excluído do zip (.distignore) e do deploy para o testes

Números e passos mapeados em #1060 (comment).

🤖 Generated with Claude Code

https://claude.ai/code/session_012XWx9qJdjZdAq8crxM9GCU


Generated by Claude Code

…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
@coveralls

Copy link
Copy Markdown

Coverage Report for CI Build 34001596359

Warning

No base build found for commit 55c58fe on develop.
Coverage changes can't be calculated without a base build.
If a base build is processing, this comment will update automatically when it completes.

Coverage: 89.899%

Details

  • Patch coverage: No coverable lines changed in this PR.

Uncovered Changes

No uncovered changes found.

Coverage Regressions

Requires a base build to compare against. How to fix this →


Coverage Stats

Coverage Status
Relevant Lines: 53867
Covered Lines: 48426
Line Coverage: 89.9%
Coverage Strength: 4.84 hits per line

💛 - Coveralls

@rpgmem
rpgmem marked this pull request as ready for review September 6, 2026 01:09
@rpgmem
rpgmem merged commit 969e847 into develop Sep 6, 2026
20 checks passed
@rpgmem
rpgmem deleted the claude/row-shapes-ruler-4vvuer branch September 6, 2026 01:09
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>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants