falsegreen-js (JavaScript / TypeScript)¶
O scanner determinístico para JS/TS. Uma varredura estática via TypeScript compiler API, sem
execução de código, agnóstica ao runner entre Jest, Vitest, Mocha+Chai, Jasmine, AVA, node:test,
Cypress, Playwright e Testing Library. Cobre .js, .jsx, .ts, .tsx, .mjs, .cjs, .mts,
.cts.
- Repositório: github.com/vinicq/falsegreen-js
- Catálogo: códigos JavaScript / TypeScript
Install¶
Use¶
npx falsegreen-js src # scan
npx falsegreen-js --format json|sarif|junit # output shape (matches the Python sibling)
npx falsegreen-js --baseline .falsegreen-baseline.json
npx falsegreen-js --config-audit # jest/vitest config for project-level false-green
O relatório JSON carrega riskGroup e oracleRegistryVersion: os códigos são classificados em
grupos de risco fechados, e o registro de oráculos (sync-fail / promise / runner-registered /
value-only) é versionado, de modo que a detecção de assíncrono é baseada em princípios em vez de
casada por string.
First finding¶
Salve um teste cuja asserção nunca roda:
// demo.test.ts
import { add } from "./add";
it("adds", () => {
expect(add(2, 2)); // sem matcher - isso não asserta nada
});
Rode o scanner sobre ele:
Ele reporta:
demo.test.ts:5 [JS2] expect() with no matcher - the assertion never runs
level: unit fix: add a matcher, e.g. .toBe(4)
Summary: 1 high, 0 low.
Reading a finding¶
Cada linha carrega os mesmos campos:
demo.test.ts:5- o arquivo e a linha que disparou o achado.[JS2]- o código do catálogo.JS2é oexpect()sem matcher. Cada código está explicado no catálogo JS/TS.level: unit- em qual nível da pirâmide de testes o arquivo está, lido a partir dos imports do arquivo.fix:- uma dica de uma linha. Aqui: adicione um matcher para a asserção realmente rodar.
--format json|sarif|junit dá um relatório legível por máquina; o SARIF sobe
para o GitHub code scanning e os achados aparecem direto no pull request.
Complete usage and configuration¶
O guia inicial acima é o caminho de cinco minutos. Esta seção é a referência completa: cada canal de instalação, cada formato de saída, cada chave de configuração, o contrato de código de saída e a integração com CI. Espelha o que o README do projeto documenta.
Install channels¶
Node 18 ou mais novo. O scanner lê .js, .jsx, .ts, .tsx, .mjs, .cjs, .mts, .cts.
npm install -D falsegreen-js # dependência de desenvolvimento do projeto
npm install -g falsegreen-js # instalação global
npx falsegreen-js . # roda a última versão do npm sem instalar
node dist/cli.js . # roda de um checkout clonado
Agnóstico de runner: o vocabulário de asserção e teste cobre Jest, Vitest, Mocha + Chai, Jasmine,
AVA, node:test, tap, Cypress, Playwright, Testing Library e Vue Test Utils, então um teste Mocha
ou AVA não é confundido com um que nunca checa nada.
Invocation¶
npx falsegreen-js # escaneia o cwd
npx falsegreen-js src test # escaneia caminhos
npx falsegreen-js --staged # só os arquivos de teste no stage do git (pre-commit)
Cada achado é reportado com seu nível na pirâmide (unit / integration / e2e, lido dos imports do arquivo) e uma dica de correção de uma linha; o resumo separa os achados por nível e lista as correções mais comuns.
Output formats¶
--format text|json|sarif|junit seleciona o formato do relatório (padrão text). --json
continua como alias de --format json. Eles casam com o irmão Python conceito a
conceito, então um pipeline pode trocar um scanner pelo outro.
npx falsegreen-js . --json # JSON legível por máquina
npx falsegreen-js . --format sarif # SARIF 2.1.0
npx falsegreen-js . --format junit # JUnit XML
npx falsegreen-js . --output report.json # grava em arquivo
npx falsegreen-js . --output .falsegreen/ # grava report.<ext> num diretório
sarifemite SARIF 2.1.0: uma regra por código presente, um resultado por achado,errorpara ALTO,warningpara BAIXO,notepara desligado. As tags do resultado carregam o julgamento (J1-J6), o grupo de risco (risk:effectiveness...) e o nível. Suba para o GitHub code scanning para ver os achados inline no PR.junitemite JUnit XML: achados ALTO viram<failure>, o resto vira<skipped>, então um reporter de CI os mostra como suíte falhando.
O relatório JSON ainda carrega riskGroup e oracleRegistryVersion: os códigos são classificados
em grupos de risco fechados, e o registro de oráculos (sync-fail / promise / runner-registered /
value-only) é versionado para que a detecção async seja principiada, não casada por string.
--output aceita arquivo ou diretório; mantenha o diretório de saída no gitignore.
Configuration¶
Desabilitar / habilitar códigos (CLI).
npx falsegreen-js --disable C7,JS3 # desliga códigos específicos
npx falsegreen-js --enable D8,M2 # reativa códigos desligados/opcionais na severidade do catálogo
npx falsegreen-js --diagnostics # inclui o grupo de manutenibilidade D*/M* como avisos
--enable liga um código desligado por padrão na severidade do catálogo; não pode subir um código
acima do catálogo. Um código passado a --enable e --disable ao mesmo tempo continua desligado,
--disable vence.
Grupo de diagnóstico (desligado por padrão). Estes não são false-green, então só avisam: D1
assertion roulette, D3 assert duplicado, D4 casos de each só por índice, D6 console.* num
teste, D7 teste anônimo, D8 número mágico, M2 corpo de teste longo. Ligue todos com
--diagnostics, ou por código via severity na config.
Supressão inline. Um comentário na linha do achado silencia um achado justificado:
Arquivo de config do projeto. falsegreen.json, .falsegreenrc.json, ou uma chave
"falsegreen" no package.json:
Precedência, da maior para a menor: --disable na CLI, --enable na CLI, disable / severity
da config, padrão do catálogo.
Auditoria de config. --config-audit é um modo separado. Em vez de escanear arquivos de teste,
lê a config de Jest / Vitest (campo jest do package.json, jest.config.*, vitest.config.*) e
reporta as formas de nível de projeto pelas quais uma suíte fica verde por configuração:
PL10-passWithNoTestspassa numa execução vazia ou filtrada até o nada.PL7- semcoverageThreshold/coverage.thresholds.PL8-bailinterrompe a execução cedo.
Baseline (catraca). Adote o scanner numa base grande sem corrigir todo achado legado de uma vez:
npx falsegreen-js --write-baseline # registra os achados atuais em .falsegreen-baseline.json, exit 0
npx falsegreen-js --baseline # reporta e falha só nos achados que não estão no baseline
--baseline [PATH] e --write-baseline [PATH] usam .falsegreen-baseline.json por padrão. A
identidade de um achado é um fingerprint de conteúdo (sha1 de caminho relativo + código + detalhe,
sem número de linha), então sobrevive a deslocamentos de linha não relacionados. Comite o baseline
e deixe o CI bloquear só nos achados novos.
Exit codes¶
| Código | Significado |
|---|---|
0 |
limpo, nenhum achado que afete o gate |
10 |
só achados de baixa confiança |
20 |
ao menos um achado de alta confiança |
Bloqueie o commit no 20.
CI integration¶
GitHub Actions. Um job que falha no exit 20:
name: falsegreen-js
on: [push, pull_request]
jobs:
scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: "20" }
- run: npx falsegreen-js . # exit 20 falha o job
Upload de SARIF para o GitHub code scanning. Emita SARIF e passe para a action do CodeQL para que os achados apareçam inline no pull request:
- run: npx falsegreen-js . --format sarif --output falsegreen.sarif
continue-on-error: true
- uses: github/codeql-action/upload-sarif@v3
with:
sarif_file: falsegreen.sarif
Hook de pre-commit. Rode o scan dos arquivos em stage como hook com --staged:
- repo: local
hooks:
- id: falsegreen-js
name: falsegreen-js
entry: npx falsegreen-js --staged
language: system
pass_filenames: false
types_or: [javascript, ts, tsx, jsx]
Para a camada que nenhum scan estático alcança (um teste verde falha quando o código está errado?), rode um teste de mutação como o Stryker. O falsegreen-js é o pré-filtro barato em todo commit; os casos semânticos (intenção, correção do oráculo) são a falsegreen-skill.
What it covers¶
Detalhe completo por código no catálogo JS/TS.
| Grupo | Códigos | Efeito |
|---|---|---|
| Compartilhado com Python (F1-F6) | C2, C2b, C5, C6, C7, C8, C9, C16, C18, C20, C21, C23, C37, C44, CC |
ALTO bloqueia, BAIXO avisa |
| Específico de JavaScript | JS1-JS9, JS11, JS13, JS15, JS17, JS18, JS21, JS22 |
idem |
| Diagnóstico / acoplamento (F8) | D1, D3, D4, D6, D7, D8, M2 |
opcional |
Projeto / CI (--config-audit) |
PL10 (--passWithNoTests), PL7 (limiar de cobertura), PL8 (bail) |
lê a config do jest/vitest |
Playwright (E2E e API)¶
Um projeto que mistura node:test/Jest/Vitest com specs de Playwright é escaneado sem ruído.
O Playwright tem uma gramática de asserção própria, e lê-la pela lente do node:test/assert
gerava falsos positivos; o scanner agora reconhece o framework e o julga pelos critérios dele.
Como o Playwright é reconhecido. Três sinais, e basta um para marcar o arquivo:
- o import do pacote (
@playwright/test,playwright,playwright-core); - uma chamada da API
test.*com namespace (test.describe,test.step,test.beforeAll,test.afterAll) — isso pega as suítes que importamtest/expectde um re-export de fixture (test.extend/mergeTests, ex.:import { test, expect } from "@company/e2e/fixtures"), o padrão que a própria doc do Playwright recomenda; - um parâmetro de fixture do Playwright:
browserNamesozinho (inequívoco), oupage/context/requestcorroborado por um matcher exclusivo do Playwright comotoBeOK/toHaveCount. Umrequestisolado nunca liga o sinal por conta própria, então um teste de supertest/Express que desestrutura{ request }não é confundido com Playwright.
O que isso corrige.
- Hooks de ciclo de vida não são testes.
test.beforeEach/afterEach/beforeAll/afterAll,test.describeetest.stepnão são mais analisados como corpo de teste, então um hook de setup ou teardown que chama código sem asserção não reporta maisC2b(e os códigos irmãos de corpo param de disparar no hook). Hook não é teste; a asserção fica notest(...).test.only/test.serial/test.fail/test.failingsão testes que rodam de verdade e mantêm a análise completa. - Skip condicional é guarda de runtime, não teste desligado.
test.skip(condição[, motivo]),test.skip()e a forma de predicadotest.skip(({ browserName }) => ...)não disparam maisJS4. Skips declarados incondicionais ainda disparam:test.skip("título", fn),test.skip(true, "...")(grupo desligado por constante),test.describe.skip,xit/xdescribe/it.todo. A supressão exige um sinal de Playwright e um primeiro argumento não-string, então umtest.skip(fn)sem título denode:test/Mocha num arquivo que não é Playwright continua disparandoJS4— sem falso negativo. - Matchers de resposta de API e de UI contam como asserção.
expect(locator).toBeVisible()/toHaveText()/toHaveCount()eexpect(apiResponse).toBeOK()já eram oráculos reais; este trabalho adiciona o tratamento de hook, skip e reconhecimento de arquivo ao redor deles, para a spec inteira — de UI ou de API — ser lida corretamente.
O sinal de reconhecimento de arquivo vale só para a decisão de skip; não amplia o nível da
pirâmide, então um arquivo Jest/Vitest com um describe simples nunca vira arquivo de camada
browser nem passa a perdoar uma checagem fraca. Veja C6 abaixo.
C6 (checagem fraca) seguro para Playwright¶
O C6 (checagem fraca) usa um modelo de oráculo único em todos os runners: só dispara
quando uma checagem de presença/não-vazio (toBeTruthy/toBeDefined/.length > 0) é o ÚNICO
oráculo do teste. Uma checagem fraca ao lado de uma asserção mais forte no mesmo teste (uma
guarda toBeGreaterThan(0) antes de um toBe(...)) não é sinalizada — a asserção forte carrega
o teste. Um teste cujas asserções são todas fracas continua reportando C6. Isso importa em
E2E, onde uma guarda como expect(items.length).toBeGreaterThan(0) costuma vir antes da
checagem real.
What it does not cover, and why¶
Fora de escopo (o eixo errado)¶
O mesmo limite do resto da família: fragilidade/falso-vermelho, higiene, lentidão, design, nomenclatura, duplicação, runtime. Veja cobertura vs a literatura.
Códigos JS deixados de fora de propósito¶
| Código | O que sinalizaria | Por que não |
|---|---|---|
| JS10 | qualquer condicional no corpo do teste | amplo demais; jest/no-conditional-in-test (ESLint) já cobre. No máximo, diagnóstico |
| JS12 | uma promise com expect não retornada/aguardada |
absorvido por JS7 |
| JS14 | um snapshot gigante | higiene (F8), não false-green |
| JS16 | teste assíncrono sem a guarda expect.assertions(n) |
a ausência de uma guarda não é um smell; sinalizá-la tem taxa de falso-positivo altíssima |
| JS19 | toBe sobre um literal de objeto/array (era toEqual) |
isto é falso-VERMELHO (uma checagem de identidade que falha em código correto): o eixo oposto |
| JS20 | uma Promise comparada sem resolves/rejects |
saber que o sujeito é uma Promise exige informação de tipo; falso positivo alto |
Não aplicável a partir do Python¶
Códigos específicos do pytest não têm análogo em JS: as regras de coleta (família C4), os
códigos de binding/escopo de pytest.raises, os códigos de capsys/os.environ. Estão ausentes
de propósito, não esquecidos.
Além do scanner¶
A fatia semântica (intenção, correção do oráculo) é a falsegreen-skill; o runtime (mutação com Stryker) fica fora de banda. Veja escopo e honestidade.