feat: fecha as brechas de assertion e adiciona configs de override (0.10.0) - #9
Merged
Merged
Conversation
`x!` is the third syntactic form of overriding the checker, alongside `as T` and `<T>x`, and it was the only one passing. It is also the cheapest to type and the most expensive to be wrong about: it compiles whether the value is nullable, whether the driver hands back another type, or whether the row simply has none. Reported under its own messageId, because the remedy differs from a cast: narrow with a check that throws, or correct a type that was never nullable in the first place. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A function that declares `any` as its return type widens every value that passes through it. That is a type assertion the reader cannot see: `as T` is greppable at the call site, an `any` return is not. This is the shape no-type-assertion pushes code into when nothing catches it — the cast does not disappear, it moves one call deeper and stops being reviewable. Observed twice on a codebase that had just been brought to green under the preset. Return position only, so it stays AST-only and ships in the preset without requiring type-aware linting. `any` on a parameter is a different defect and belongs to @typescript-eslint/no-explicit-any. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Some classes are nominal and offer no discriminant to switch on: a
framework exception, a value object from another module, an Error
subclass. The check has to happen somewhere, and a `value is X`
signature is the one place it states what it is doing — the answer
leaves as a narrowed type instead of a bare boolean, and the project
ends up with one greppable guard per class.
Without this, the cheapest way out of the rule was structural duck
typing: `'toDate' in value` instead of `value instanceof IsoDate`. That
passes the linter, passes for any object that happens to carry the
member, and is strictly worse than what it replaced. The exemption makes
the honest path the cheap one.
Only the innermost enclosing function counts, so a guard cannot lend its
exemption to the code that follows it. Off via `{ allowTypeGuards:
false }`. The message now names the class, so the guard it asks for
comes with a signature.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
… out The old wording was read as licence to return a zero-or-one array, which is a null in a box: the type promises a list it will never have more than one of, and every caller writes a loop that runs once. An empty collection models absence only where the return type was already a collection. Otherwise: throw, or return an object that answers for the absent case. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Adds no-any-return to the rules table and rule details, spells out the non-null operator under no-type-assertion, and documents allowTypeGuards with the duck-typing workaround it exists to price out. The adoption section says plainly that these two are not in the 1,261-file measurement, and carries the numbers actually measured: 2 reports for `x!` and 0 for no-any-return over a fourth service, and one `Promise<any>` found on a tree that already linted clean under starter. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…judge Two override configs, alongside the recommended/starter presets. `off` is every rule this plugin ships, disabled, for files nobody writes by hand — a migration a CLI scaffolds, a build script whose interface with the operator is `console`. Derived from the plugin's own rule list, so a rule added later arrives already silent there; the hand-rolled equivalent, mapping over Object.keys(elegant.rules) in a consumer's config, goes stale the moment it is written. `tests` is the eight rules a spec legitimately trips. Which eight is a measurement, not a taste: over the corpus in "Adopting on an existing codebase", these are the rules that report inside test files, each for a reason that holds there and nowhere else. The class-shape rules are deliberately left out — they report zero times in specs on that corpus, so disabling them buys nothing and costs the report on the day a spec finally earns one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Rewrites "Relaxing rules in test files" around the measured list rather than the guessed one, and says why the rules left out were left out. Adds "Generated and scaffolded files" for `off`. Also answers the case no-logic-in-constructor could not: on a class a DI container builds there is no static factory to move the work to, so the rule's stated remedy does not exist. The move people reach for — a lifecycle hook — trades the rule for a worse invariant, a field that is no longer readonly and is empty until the hook runs. Resolve the config in the module's useFactory and inject the result instead. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Uma tag não é prova de que a versão não foi publicada: dá para publicar de uma máquina antes de empurrar a tag, ou reempurrar a tag depois de um run que falhou. Foi o que aconteceu com a v0.10.0 — publicada à mão, e o job morreu em "cannot publish over the previously published versions", que lê como build quebrado quando nada está quebrado. Consulta o registry antes e pula o publish se a versão já existe. Um `npm view` que falhe por qualquer outro motivo deixa a flag sem valor, e o publish roda e falha alto — o default seguro. O skip fica registrado no summary do run, para não virar sucesso silencioso. Não conserta o run que já falhou: para tag push o GitHub usa o workflow do commit taggeado. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
0.10.0— fecha duas brechas que deixavam o lint verde com código pior, e adiciona duas configs de override.Important
0.10.0já está publicado no npm e a tagv0.10.0neste branch é a origem do que foi publicado. Fazer merge commit ou rebase merge — um squash orfanaria o commit que a tag referencia, e o pacote público ficaria sem fonte correspondente no histórico.260 testes (eram 239),
typechecklimpo, o plugin passa no próprio lint.As brechas
no-type-assertionnão cobriax!. As três formas sintáticas de passar por cima do checker são o mesmo ato, e a não-nula era a única livre — também é a mais barata de escrever e a mais cara de estar errada. Reportada com messageId próprio, porque o remédio difere de um cast: estreitar com um check que lança, ou corrigir um tipo que nunca foi nullable.Nada impedia lavar o cast através de
any. Comno-type-assertionligado e nada mais,as Tsome do call site e reaparece como uma função que devolveany— o cast não desaparece, ele desce um nível e deixa de ser revisável.no-any-returnfecha isso na posição de retorno, mantendo-se AST-only para não exigir lint com type info.Observado num codebase real: um repositório que tinha acabado de chegar ao verde sob
starterainda tinha umPromise<any>que absorvera um cast removido na passagem.no-instanceofagora permite type guard declaradoNão é afrouxamento — é preço. Sem isso, a saída mais barata da regra era duck typing estrutural (
'toDate' in valueno lugar devalue instanceof IsoDate): passa no linter, passa para qualquer objeto que por acaso tenha o membro, e é pior que o que substituiu.Com a exceção, o caminho honesto passa a ser o barato.
instanceofé permitido dentro de função cujo retorno évalue is X— a assinatura concentra a pergunta nominal numa função nomeada e greppável, uma por classe. Só a função mais interna conta, então um guard não empresta a exceção para o código seguinte. Uma função que devolvebooleannão é guard. Desligável com{ allowTypeGuards: false }.configs.testseconfigs.offOverrides, não presets.
offé toda regra desligada, para arquivo que ninguém escreve à mão — migration que o CLI gera, script de build cuja interface com o operador éconsole. Derivada da lista de regras do próprio plugin, então regra nova nasce silenciada nesses arquivos; o equivalente hand-rolled no config do consumidor fica desatualizado no instante em que é escrito.testssão as oito regras que um spec legitimamente dispara — e quais oito é medição, não gosto. Sobre o corpus da seção de adoção, são as que de fato reportam dentro de arquivo de teste. As regras de forma de classe ficaram deliberadamente de fora: reportam zero vezes em spec naquele corpus, então desligá-las não compra nada e custa o report no dia em que um spec finalmente merecer um.no-null-return: só mensagemA redação antiga ("return an explicit empty value") era lida como licença para devolver array de zero ou um elemento. Isso é um
nullnuma caixa: o tipo promete uma lista que nunca terá mais de um item, e o chamador escreve um laço que roda uma vez. A nova diz que coleção vazia só modela ausência onde o retorno já era coleção.🤖 Generated with Claude Code