Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/catch-refail-tap-error.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/tsgo": minor
---

Add the v4-only `catchRefailToTapError` diagnostic. It suggests `Effect.tapError` when an `Effect.catch` handler sequences an effect with `Effect.andThen` or a zero-argument `Effect.flatMap` callback and then fails with the original, unmodified error. Generator handlers and selective catches are excluded.
5 changes: 5 additions & 0 deletions .changeset/strict-diagnostic-preset.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@effect/tsgo": minor
---

Add a `strict` diagnostic preset that promotes every default-enabled diagnostic to an error. Expose the complete preset catalog consistently through TypeScript setup and the generated Oxlint presets.
7 changes: 7 additions & 0 deletions .changeset/unknown-diagnostic-rule.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
"@effect/tsgo": minor
---

Warn when `diagnosticSeverity` contains an unknown Effect rule name, including in overrides and inherited configurations. For example, `"floatingEfect": "error"` now reports `effect(unknownRuleName)` instead of being silently ignored.

The check uses the fully merged configuration. Set `"unknownRuleName": "off"` to disable it or `"unknownRuleName": "error"` to raise its severity. Local keys are underlined in tsconfig; inherited keys without local syntax produce a diagnostic without a source location.
2 changes: 1 addition & 1 deletion .changeset/update-upstreams.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,4 @@
"@effect/tsgo": patch
---

Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260913.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/879f9867ac455404e75759dd1739281cf6aa7f85) commit `879f9867ac455404e75759dd1739281cf6aa7f85`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2).
Update the TypeScript next tag to [`typescript@next`](https://www.npmjs.com/package/typescript/v/7.1.0-dev.20260916.1), which ships [`typescript-go`](https://github.com/microsoft/typescript-go/commit/d2b20b35034bd902bdda5974f522ff00352895e9) commit `d2b20b35034bd902bdda5974f522ff00352895e9`, and update the TypeScript latest tag to [`typescript@latest`](https://www.npmjs.com/package/typescript/v/7.0.2).
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,7 @@ Some diagnostics are off by default or have a default severity of suggestion, bu
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-conditional-refail-to-catch-if.md"><code>catchConditionalRefailToCatchIf</code></a></td><td>Suggests Effect.catchIf, Effect.catchCauseIf, or Effect.catchTag for conditional catch handlers that re-fail their untouched input</td></tr>
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-die-to-or-die.md"><code>catchDieToOrDie</code></a></td><td>Suggests using Effect.orDie instead of Effect.catch or Effect.catchAll with an identity-forwarding Effect.die handler</td></tr>
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-if-tag-to-catch-tag.md"><code>catchIfTagToCatchTag</code></a></td><td>Suggests Effect.catchTag instead of Effect.catchIf with a direct _tag equality predicate</td></tr>
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-refail-to-tap-error.md"><code>catchRefailToTapError</code></a></td><td>Suggests Effect.tapError for catch handlers that sequence an effect and then re-fail the original error</td></tr>
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-tag-to-catch-reason.md"><code>catchTagToCatchReason</code></a></td><td>Suggests Effect.catchReason or Effect.catchReasons for handlers that re-fail unmatched reason._tag branches</td></tr>
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-to-ignore.md"><code>catchToIgnore</code></a></td><td>Suggests using Effect.ignore or Effect.ignoreCause instead of Effect.catch/catchCause returning Effect.void</td></tr>
<tr><td><a href="https://github.com/Effect-TS/tsgo/blob/main/docs/rules/catch-to-or-else-succeed.md"><code>catchToOrElseSucceed</code></a></td><td>Suggests using Effect.orElseSucceed instead of Effect.catch + Effect.succeed</td></tr>
Expand Down
2 changes: 1 addition & 1 deletion _packages/tsgo/src/cli/presets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ interface MetadataDocument {
readonly presets: ReadonlyArray<DiagnosticPreset>
}

const metadata = metadataJson as MetadataDocument
const metadata = metadataJson as unknown as MetadataDocument

const severityRank: Record<RuleSeverity, number> = {
off: 0,
Expand Down
2 changes: 1 addition & 1 deletion _packages/tsgo/src/cli/setup/rule-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export function getAllGroups(): ReadonlyArray<GroupInfo> {
}

export function getAllPresets(): ReadonlyArray<DiagnosticPresetInfo> {
return (metadataJson as { presets?: ReadonlyArray<DiagnosticPresetInfo> }).presets ?? []
return (metadataJson as unknown as { presets?: ReadonlyArray<DiagnosticPresetInfo> }).presets ?? []
}

export function cycleSeverity(
Expand Down
307 changes: 307 additions & 0 deletions _packages/tsgo/src/metadata.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,231 @@
}
],
"presets": [
{
"name": "recommended",
"description": "Enable diagnostics at their default severities.",
"diagnosticSeverity": {
"abortControllerInEffect": "suggestion",
"acquireReleaseDisposable": "suggestion",
"allOfMapToForEach": "suggestion",
"catchAllTagDispatchToCatchTag": "suggestion",
"catchAllToMapError": "suggestion",
"catchChainToFirstSuccessOf": "suggestion",
"catchConditionalRefailToCatchIf": "suggestion",
"catchDieToOrDie": "suggestion",
"catchIfTagToCatchTag": "suggestion",
"catchRefailToTapError": "suggestion",
"catchTagToCatchReason": "suggestion",
"catchToIgnore": "suggestion",
"catchToOrElseSucceed": "suggestion",
"catchUnfailableEffect": "suggestion",
"classSelfMismatch": "error",
"duplicatePackage": "warning",
"effectFnIife": "warning",
"effectFnImplicitAny": "error",
"effectFnOpportunity": "suggestion",
"effectGenUsesAdapter": "warning",
"effectInFailure": "warning",
"effectInVoidSuccess": "warning",
"effectMapFlatten": "suggestion",
"effectMapVoid": "suggestion",
"effectSucceedWithVoid": "suggestion",
"flatMapConditionalToFilterOrFail": "suggestion",
"flatMapIgnoredParamToAndThen": "suggestion",
"flatMapToMap": "suggestion",
"floatingEffect": "error",
"floatingEffectInVitest": "error",
"genericEffectServices": "warning",
"globalErrorInEffectCatch": "warning",
"globalErrorInEffectFailure": "warning",
"layerMergeAllWithDependencies": "warning",
"lazyEffect": "suggestion",
"lazyPromiseInEffectSync": "warning",
"leakingRequirements": "suggestion",
"mapSomeToAsSome": "suggestion",
"matchEffectToMapBoth": "suggestion",
"matchEffectToMatch": "suggestion",
"missingEffectContext": "error",
"missingEffectError": "error",
"missingLayerContext": "error",
"missingReturnYieldStar": "error",
"missingStarInYieldEffectGen": "error",
"multipleCatchTag": "suggestion",
"multipleEffectProvide": "warning",
"nonObjectEffectServiceType": "error",
"obsoleteMatchImport": "warning",
"obsoleteSchemaImport": "warning",
"optionMatchToFromOption": "suggestion",
"outdatedApi": "warning",
"overriddenSchemaConstructor": "error",
"preferSucceedSomeOrNone": "suggestion",
"preferTypedSchemaDecoder": "suggestion",
"preferUnsafeConstructor": "suggestion",
"promiseInEffectSuccess": "warning",
"provideLayerSucceedToProvideService": "suggestion",
"raceFirstWithSleepToTimeout": "suggestion",
"redundantMapError": "suggestion",
"redundantOrDie": "suggestion",
"redundantSchemaTagIdentifier": "suggestion",
"returnEffectInGen": "suggestion",
"runEffectInsideEffect": "suggestion",
"runOfExitToRunExit": "suggestion",
"schemaLiteralNonFinite": "error",
"schemaNumber": "suggestion",
"schemaOpaqueInstanceMember": "error",
"schemaStructWithTag": "suggestion",
"schemaSyncInEffect": "suggestion",
"scopeInLayerEffect": "warning",
"syncToSucceed": "suggestion",
"timeoutCatchTagToTimeoutOrElse": "suggestion",
"tryCatchInEffectGen": "suggestion",
"unknownInEffectCatch": "warning",
"unnecessaryEffectGen": "suggestion",
"unnecessaryFailYieldableError": "suggestion",
"unnecessaryPipe": "suggestion",
"unnecessaryPipeChain": "suggestion",
"unnecessaryTypeofType": "suggestion"
}
},
{
"name": "strict",
"description": "Treat every diagnostic enabled by default as an error.",
"diagnosticSeverity": {
"abortControllerInEffect": "error",
"acquireReleaseDisposable": "error",
"allOfMapToForEach": "error",
"catchAllTagDispatchToCatchTag": "error",
"catchAllToMapError": "error",
"catchChainToFirstSuccessOf": "error",
"catchConditionalRefailToCatchIf": "error",
"catchDieToOrDie": "error",
"catchIfTagToCatchTag": "error",
"catchRefailToTapError": "error",
"catchTagToCatchReason": "error",
"catchToIgnore": "error",
"catchToOrElseSucceed": "error",
"catchUnfailableEffect": "error",
"classSelfMismatch": "error",
"duplicatePackage": "error",
"effectFnIife": "error",
"effectFnImplicitAny": "error",
"effectFnOpportunity": "error",
"effectGenUsesAdapter": "error",
"effectInFailure": "error",
"effectInVoidSuccess": "error",
"effectMapFlatten": "error",
"effectMapVoid": "error",
"effectSucceedWithVoid": "error",
"flatMapConditionalToFilterOrFail": "error",
"flatMapIgnoredParamToAndThen": "error",
"flatMapToMap": "error",
"floatingEffect": "error",
"floatingEffectInVitest": "error",
"genericEffectServices": "error",
"globalErrorInEffectCatch": "error",
"globalErrorInEffectFailure": "error",
"layerMergeAllWithDependencies": "error",
"lazyEffect": "error",
"lazyPromiseInEffectSync": "error",
"leakingRequirements": "error",
"mapSomeToAsSome": "error",
"matchEffectToMapBoth": "error",
"matchEffectToMatch": "error",
"missingEffectContext": "error",
"missingEffectError": "error",
"missingLayerContext": "error",
"missingReturnYieldStar": "error",
"missingStarInYieldEffectGen": "error",
"multipleCatchTag": "error",
"multipleEffectProvide": "error",
"nonObjectEffectServiceType": "error",
"obsoleteMatchImport": "error",
"obsoleteSchemaImport": "error",
"optionMatchToFromOption": "error",
"outdatedApi": "error",
"overriddenSchemaConstructor": "error",
"preferSucceedSomeOrNone": "error",
"preferTypedSchemaDecoder": "error",
"preferUnsafeConstructor": "error",
"promiseInEffectSuccess": "error",
"provideLayerSucceedToProvideService": "error",
"raceFirstWithSleepToTimeout": "error",
"redundantMapError": "error",
"redundantOrDie": "error",
"redundantSchemaTagIdentifier": "error",
"returnEffectInGen": "error",
"runEffectInsideEffect": "error",
"runOfExitToRunExit": "error",
"schemaLiteralNonFinite": "error",
"schemaNumber": "error",
"schemaOpaqueInstanceMember": "error",
"schemaStructWithTag": "error",
"schemaSyncInEffect": "error",
"scopeInLayerEffect": "error",
"syncToSucceed": "error",
"timeoutCatchTagToTimeoutOrElse": "error",
"tryCatchInEffectGen": "error",
"unknownInEffectCatch": "error",
"unnecessaryEffectGen": "error",
"unnecessaryFailYieldableError": "error",
"unnecessaryPipe": "error",
"unnecessaryPipeChain": "error",
"unnecessaryTypeofType": "error"
}
},
{
"name": "correctness",
"description": "Enable all correctness diagnostics at warning level.",
"diagnosticSeverity": {
"anyUnknownInErrorContext": "warning",
"classSelfMismatch": "warning",
"duplicatePackage": "warning",
"effectFnImplicitAny": "warning",
"floatingEffect": "warning",
"floatingEffectInVitest": "warning",
"genericEffectServices": "warning",
"missingEffectContext": "warning",
"missingEffectError": "warning",
"missingLayerContext": "warning",
"missingReturnYieldStar": "warning",
"missingStarInYieldEffectGen": "warning",
"nonObjectEffectServiceType": "warning",
"obsoleteMatchImport": "warning",
"obsoleteSchemaImport": "warning",
"outdatedApi": "warning",
"overriddenSchemaConstructor": "warning",
"promiseInEffectSuccess": "warning",
"schemaLiteralNonFinite": "warning",
"schemaOpaqueInstanceMember": "warning",
"unsafeEffectTypeAssertion": "warning"
}
},
{
"name": "antipattern",
"description": "Enable all anti-pattern diagnostics at warning level.",
"diagnosticSeverity": {
"catchUnfailableEffect": "warning",
"effectFnIife": "warning",
"effectGenUsesAdapter": "warning",
"effectInFailure": "warning",
"effectInVoidSuccess": "warning",
"globalErrorInEffectCatch": "warning",
"globalErrorInEffectFailure": "warning",
"layerMergeAllWithDependencies": "warning",
"lazyEffect": "warning",
"lazyPromiseInEffectSync": "warning",
"leakingRequirements": "warning",
"multipleEffectProvide": "warning",
"preferUnsafeConstructor": "warning",
"returnEffectInGen": "warning",
"runEffectInsideEffect": "warning",
"schemaSyncInEffect": "warning",
"scopeInLayerEffect": "warning",
"strictEffectProvide": "warning",
"tryCatchInEffectGen": "warning",
"unknownInEffectCatch": "warning"
}
},
{
"name": "effect-native",
"description": "Enable all Effect-native diagnostics at warning level.",
Expand Down Expand Up @@ -49,6 +274,65 @@
"processEnvInEffect": "warning",
"schemaSync": "warning"
}
},
{
"name": "style",
"description": "Enable all style diagnostics at warning level.",
"diagnosticSeverity": {
"acquireReleaseDisposable": "warning",
"allOfMapToForEach": "warning",
"catchAllTagDispatchToCatchTag": "warning",
"catchAllToMapError": "warning",
"catchChainToFirstSuccessOf": "warning",
"catchConditionalRefailToCatchIf": "warning",
"catchDieToOrDie": "warning",
"catchIfTagToCatchTag": "warning",
"catchRefailToTapError": "warning",
"catchTagToCatchReason": "warning",
"catchToIgnore": "warning",
"catchToOrElseSucceed": "warning",
"deterministicKeys": "warning",
"effectDoNotation": "warning",
"effectFnOpportunity": "warning",
"effectMapFlatten": "warning",
"effectMapVoid": "warning",
"effectSucceedWithVoid": "warning",
"flatMapConditionalToFilterOrFail": "warning",
"flatMapIgnoredParamToAndThen": "warning",
"flatMapToMap": "warning",
"mapSomeToAsSome": "warning",
"matchEffectToMapBoth": "warning",
"matchEffectToMatch": "warning",
"missedPipeableOpportunity": "warning",
"missingEffectServiceDependency": "warning",
"missingPipeableSignature": "warning",
"multipleCatchTag": "warning",
"nestedEffectGenYield": "warning",
"newSchemaClass": "warning",
"optionMatchToFromOption": "warning",
"preferSchemaTypeProperty": "warning",
"preferSucceedSomeOrNone": "warning",
"preferTypedSchemaDecoder": "warning",
"provideLayerSucceedToProvideService": "warning",
"raceFirstWithSleepToTimeout": "warning",
"redundantMapError": "warning",
"redundantOrDie": "warning",
"redundantSchemaTagIdentifier": "warning",
"runOfExitToRunExit": "warning",
"schemaNumber": "warning",
"schemaStructWithTag": "warning",
"schemaUnionOfLiterals": "warning",
"serviceNotAsClass": "warning",
"strictBooleanExpressions": "warning",
"syncToSucceed": "warning",
"timeoutCatchTagToTimeoutOrElse": "warning",
"unnecessaryArrowBlock": "warning",
"unnecessaryEffectGen": "warning",
"unnecessaryFailYieldableError": "warning",
"unnecessaryPipe": "warning",
"unnecessaryPipeChain": "warning",
"unnecessaryTypeofType": "warning"
}
}
],
"rules": [
Expand Down Expand Up @@ -1771,6 +2055,29 @@
]
}
},
{
"name": "catchRefailToTapError",
"group": "style",
"description": "Suggests Effect.tapError for catch handlers that sequence an effect and then re-fail the original error",
"defaultSeverity": "suggestion",
"fixable": false,
"supportedEffect": [
"v4"
],
"codes": [
377133
],
"preview": {
"sourceText": "import { Effect } from \"effect\"\n\ndeclare const save: Effect.Effect\u003cvoid, Error\u003e\ndeclare const rollback: Effect.Effect\u003cvoid\u003e\n\nexport const program = save.pipe(\n Effect.catch(error =\u003e rollback.pipe(Effect.andThen(Effect.fail(error))))\n)\n",
"diagnostics": [
{
"start": 161,
"end": 173,
"text": "Use Effect.tapError to observe the error and preserve the original failure without having to re-fail it. effect(catchRefailToTapError)"
}
]
}
},
{
"name": "catchTagToCatchReason",
"group": "style",
Expand Down
Loading