diff --git a/.changeset/strict-diagnostic-preset.md b/.changeset/strict-diagnostic-preset.md new file mode 100644 index 00000000..4aa5167f --- /dev/null +++ b/.changeset/strict-diagnostic-preset.md @@ -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. diff --git a/_packages/tsgo/src/cli/presets.ts b/_packages/tsgo/src/cli/presets.ts index 992ade98..73c7ad69 100644 --- a/_packages/tsgo/src/cli/presets.ts +++ b/_packages/tsgo/src/cli/presets.ts @@ -18,7 +18,7 @@ interface MetadataDocument { readonly presets: ReadonlyArray } -const metadata = metadataJson as MetadataDocument +const metadata = metadataJson as unknown as MetadataDocument const severityRank: Record = { off: 0, diff --git a/_packages/tsgo/src/cli/setup/rule-info.ts b/_packages/tsgo/src/cli/setup/rule-info.ts index ae2502e7..c684ce34 100644 --- a/_packages/tsgo/src/cli/setup/rule-info.ts +++ b/_packages/tsgo/src/cli/setup/rule-info.ts @@ -39,7 +39,7 @@ export function getAllGroups(): ReadonlyArray { } export function getAllPresets(): ReadonlyArray { - return (metadataJson as { presets?: ReadonlyArray }).presets ?? [] + return (metadataJson as unknown as { presets?: ReadonlyArray }).presets ?? [] } export function cycleSeverity( diff --git a/_packages/tsgo/src/metadata.json b/_packages/tsgo/src/metadata.json index d00bbb9b..76b87cf5 100644 --- a/_packages/tsgo/src/metadata.json +++ b/_packages/tsgo/src/metadata.json @@ -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.", @@ -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": [ diff --git a/_packages/tsgo/test/presets.test.ts b/_packages/tsgo/test/presets.test.ts index f85297c0..1b27ace5 100644 --- a/_packages/tsgo/test/presets.test.ts +++ b/_packages/tsgo/test/presets.test.ts @@ -8,6 +8,31 @@ import { } from "../src/cli/presets.js" describe("diagnostic presets", () => { + it("exposes the complete preset catalog", () => { + expect(presets.map((preset) => preset.name)).toEqual([ + "recommended", + "strict", + "correctness", + "antipattern", + "effect-native", + "style" + ]) + }) + + it("makes every diagnostic enabled by default an error in strict mode", () => { + const strict = presets.find((preset) => preset.name === "strict")! + + expect(strict.diagnosticSeverity).not.toEqual({}) + expect(Object.values(strict.diagnosticSeverity).every((severity) => severity === "error")).toBe(true) + }) + + it("uses only default-enabled diagnostics in the recommended preset", () => { + const recommended = presets.find((preset) => preset.name === "recommended")! + + expect(recommended.diagnosticSeverity.floatingEffect).toBe("error") + expect(recommended.diagnosticSeverity.globalDate).toBeUndefined() + }) + it("merges the selected preset severities", () => { expect(mergePresetDiagnosticSeverities(["effect-native"])).toEqual( presets.find((preset) => preset.name === "effect-native")!.diagnosticSeverity diff --git a/docs/README.md b/docs/README.md index cc7cc401..38c56cb2 100644 --- a/docs/README.md +++ b/docs/README.md @@ -41,7 +41,7 @@ Run pnpm install to install the dependencies and run the prepare script to patch pnpm install ``` -Effect rules require Oxlint's type-aware mode and the `effecttsgo` plugin. The recommended preset enables both and configures the recommended Effect rules. Use the schema shipped with `@effect/tsgo` for validation and completions: +Effect rules require Oxlint's type-aware mode and the `effecttsgo` plugin. The recommended preset enables both and configures every default-enabled Effect rule. Use the schema shipped with `@effect/tsgo` for validation and completions: ```json { @@ -52,7 +52,7 @@ Effect rules require Oxlint's type-aware mode and the `effecttsgo` plugin. The r } ``` -The package also provides presets for each diagnostic category: `correctness`, `antipattern`, `effect-native`, and `style`. Extended configurations are applied in order, and rules in the project configuration take precedence, so categories can be combined and individual rules can be adjusted: +The package also provides presets for each diagnostic category: `correctness`, `antipattern`, `effect-native`, and `style`. The `strict` preset promotes every diagnostic enabled by default to an error. Extended configurations are applied in order, and rules in the project configuration take precedence, so presets can be combined and individual rules can be adjusted: ```json { diff --git a/etscore/oxlint_schema_test.go b/etscore/oxlint_schema_test.go index 14d31236..a0e5ed22 100644 --- a/etscore/oxlint_schema_test.go +++ b/etscore/oxlint_schema_test.go @@ -112,35 +112,16 @@ type oxlintPresetOptions struct { } func generateOxlintPresets() (map[string][]byte, error) { - groups := rules.MetadataGroups() - generated := make(map[string][]byte, len(groups)+1) - - recommendedSeverities := make(map[string]string) - for _, current := range rules.All { - if severity := oxlintSeverity(current.DefaultSeverity); severity != "off" { - recommendedSeverities[oxlintRuleName(current.Name)] = severity - } - } - for _, preset := range rules.MetadataPresets() { - for name, severity := range preset.DiagnosticSeverity { - oxlintName := oxlintRuleName(name) - if current, next := recommendedSeverities[oxlintName], oxlintSeverity(severity); current != "error" && next != "off" { - recommendedSeverities[oxlintName] = next - } - } - } - if err := addOxlintPreset(generated, "recommended.json", recommendedSeverities); err != nil { - return nil, err - } - - for _, group := range groups { - severities := make(map[string]string) - for _, current := range rules.All { - if current.Group == group.ID { - severities[oxlintRuleName(current.Name)] = "warn" - } + diagnosticPresets := rules.MetadataPresets() + generated := make(map[string][]byte, len(diagnosticPresets)) + + for _, preset := range diagnosticPresets { + name := oxlintRuleName(preset.Name) + ".json" + severities := make(map[string]string, len(preset.DiagnosticSeverity)) + for ruleName, severity := range preset.DiagnosticSeverity { + severities[oxlintRuleName(ruleName)] = oxlintSeverity(severity) } - if err := addOxlintPreset(generated, oxlintRuleName(group.ID)+".json", severities); err != nil { + if err := addOxlintPreset(generated, name, severities); err != nil { return nil, err } } diff --git a/internal/rules/metadata.go b/internal/rules/metadata.go index dfb3f82d..1cdb56ea 100644 --- a/internal/rules/metadata.go +++ b/internal/rules/metadata.go @@ -19,11 +19,38 @@ func MetadataGroups() []rule.MetadataGroup { } func MetadataPresets() []rule.MetadataPreset { - return []rule.MetadataPreset{{ - Name: "effect-native", - Description: "Enable all Effect-native diagnostics at warning level.", - DiagnosticSeverity: buildGroupPreset("effectNative", etscore.SeverityWarning), - }} + return []rule.MetadataPreset{ + { + Name: "recommended", + Description: "Enable diagnostics at their default severities.", + DiagnosticSeverity: buildDefaultPreset(), + }, + { + Name: "strict", + Description: "Treat every diagnostic enabled by default as an error.", + DiagnosticSeverity: buildStrictPreset(), + }, + { + Name: "correctness", + Description: "Enable all correctness diagnostics at warning level.", + DiagnosticSeverity: buildGroupPreset("correctness", etscore.SeverityWarning), + }, + { + Name: "antipattern", + Description: "Enable all anti-pattern diagnostics at warning level.", + DiagnosticSeverity: buildGroupPreset("antipattern", etscore.SeverityWarning), + }, + { + Name: "effect-native", + Description: "Enable all Effect-native diagnostics at warning level.", + DiagnosticSeverity: buildGroupPreset("effectNative", etscore.SeverityWarning), + }, + { + Name: "style", + Description: "Enable all style diagnostics at warning level.", + DiagnosticSeverity: buildGroupPreset("style", etscore.SeverityWarning), + }, + } } func buildGroupPreset(group string, severity etscore.Severity) map[string]etscore.Severity { @@ -35,3 +62,21 @@ func buildGroupPreset(group string, severity etscore.Severity) map[string]etscor } return preset } + +func buildStrictPreset() map[string]etscore.Severity { + preset := buildDefaultPreset() + for name := range preset { + preset[name] = etscore.SeverityError + } + return preset +} + +func buildDefaultPreset() map[string]etscore.Severity { + preset := make(map[string]etscore.Severity) + for _, current := range All { + if !current.DefaultSeverity.IsOff() { + preset[current.Name] = current.DefaultSeverity + } + } + return preset +} diff --git a/internal/rules/metadata_test.go b/internal/rules/metadata_test.go new file mode 100644 index 00000000..17aa5180 --- /dev/null +++ b/internal/rules/metadata_test.go @@ -0,0 +1,97 @@ +package rules + +import ( + "slices" + "testing" + + "github.com/effect-ts/tsgo/etscore" +) + +func TestStrictPreset(t *testing.T) { + t.Parallel() + + strict := metadataPresetByName(t, "strict") + + for _, current := range All { + severity, included := strict[current.Name] + if current.DefaultSeverity == etscore.SeverityOff { + if included { + t.Errorf("off-by-default diagnostic %q is included", current.Name) + } + continue + } + if !included { + t.Errorf("enabled-by-default diagnostic %q is missing", current.Name) + } else if severity != etscore.SeverityError { + t.Errorf("diagnostic %q has severity %q, want error", current.Name, severity) + } + } +} + +func TestRecommendedPreset(t *testing.T) { + t.Parallel() + + recommended := metadataPresetByName(t, "recommended") + for _, current := range All { + severity, included := recommended[current.Name] + switch { + case current.DefaultSeverity.IsOff(): + if included { + t.Errorf("off-by-default diagnostic %q is included", current.Name) + } + case !included: + t.Errorf("enabled-by-default diagnostic %q is missing", current.Name) + case severity != current.DefaultSeverity: + t.Errorf("diagnostic %q has severity %q, want default %q", current.Name, severity, current.DefaultSeverity) + } + } +} + +func TestMetadataPresetCatalog(t *testing.T) { + t.Parallel() + + presets := MetadataPresets() + names := make([]string, len(presets)) + for index, preset := range presets { + names[index] = preset.Name + } + want := []string{"recommended", "strict", "correctness", "antipattern", "effect-native", "style"} + if !slices.Equal(names, want) { + t.Fatalf("preset names = %v, want %v", names, want) + } + + groups := map[string]string{ + "correctness": "correctness", + "antipattern": "antipattern", + "effect-native": "effectNative", + "style": "style", + } + for presetName, group := range groups { + preset := metadataPresetByName(t, presetName) + for _, current := range All { + severity, included := preset[current.Name] + if current.Group != group { + if included { + t.Errorf("preset %q includes diagnostic %q from group %q", presetName, current.Name, current.Group) + } + continue + } + if !included { + t.Errorf("preset %q is missing diagnostic %q", presetName, current.Name) + } else if severity != etscore.SeverityWarning { + t.Errorf("preset %q diagnostic %q has severity %q, want warning", presetName, current.Name, severity) + } + } + } +} + +func metadataPresetByName(t *testing.T, name string) map[string]etscore.Severity { + t.Helper() + for _, preset := range MetadataPresets() { + if preset.Name == name { + return preset.DiagnosticSeverity + } + } + t.Fatalf("preset %q not found", name) + return nil +} diff --git a/oxlint-presets/recommended.json b/oxlint-presets/recommended.json index f95a5ba7..e77da26c 100644 --- a/oxlint-presets/recommended.json +++ b/oxlint-presets/recommended.json @@ -9,7 +9,6 @@ "effecttsgo/abort-controller-in-effect": "warn", "effecttsgo/acquire-release-disposable": "warn", "effecttsgo/all-of-map-to-for-each": "warn", - "effecttsgo/async-function": "warn", "effecttsgo/catch-all-tag-dispatch-to-catch-tag": "warn", "effecttsgo/catch-all-to-map-error": "warn", "effecttsgo/catch-chain-to-first-success-of": "warn", @@ -22,8 +21,6 @@ "effecttsgo/catch-to-or-else-succeed": "warn", "effecttsgo/catch-unfailable-effect": "warn", "effecttsgo/class-self-mismatch": "error", - "effecttsgo/crypto-random-uuid": "warn", - "effecttsgo/crypto-random-uuid-in-effect": "warn", "effecttsgo/duplicate-package": "warn", "effecttsgo/effect-fn-iife": "warn", "effecttsgo/effect-fn-implicit-any": "error", @@ -34,26 +31,14 @@ "effecttsgo/effect-map-flatten": "warn", "effecttsgo/effect-map-void": "warn", "effecttsgo/effect-succeed-with-void": "warn", - "effecttsgo/extends-native-error": "warn", "effecttsgo/flat-map-conditional-to-filter-or-fail": "warn", "effecttsgo/flat-map-ignored-param-to-and-then": "warn", "effecttsgo/flat-map-to-map": "warn", "effecttsgo/floating-effect": "error", "effecttsgo/floating-effect-in-vitest": "error", "effecttsgo/generic-effect-services": "warn", - "effecttsgo/global-console": "warn", - "effecttsgo/global-console-in-effect": "warn", - "effecttsgo/global-date": "warn", - "effecttsgo/global-date-in-effect": "warn", "effecttsgo/global-error-in-effect-catch": "warn", "effecttsgo/global-error-in-effect-failure": "warn", - "effecttsgo/global-fetch": "warn", - "effecttsgo/global-fetch-in-effect": "warn", - "effecttsgo/global-random": "warn", - "effecttsgo/global-random-in-effect": "warn", - "effecttsgo/global-timers": "warn", - "effecttsgo/global-timers-in-effect": "warn", - "effecttsgo/instance-of-schema": "warn", "effecttsgo/layer-merge-all-with-dependencies": "warn", "effecttsgo/lazy-effect": "warn", "effecttsgo/lazy-promise-in-effect-sync": "warn", @@ -68,20 +53,15 @@ "effecttsgo/missing-star-in-yield-effect-gen": "error", "effecttsgo/multiple-catch-tag": "warn", "effecttsgo/multiple-effect-provide": "warn", - "effecttsgo/new-promise": "warn", - "effecttsgo/node-builtin-import": "warn", "effecttsgo/non-object-effect-service-type": "error", "effecttsgo/obsolete-match-import": "warn", "effecttsgo/obsolete-schema-import": "warn", "effecttsgo/option-match-to-from-option": "warn", "effecttsgo/outdated-api": "warn", "effecttsgo/overridden-schema-constructor": "error", - "effecttsgo/prefer-schema-over-json": "warn", "effecttsgo/prefer-succeed-some-or-none": "warn", "effecttsgo/prefer-typed-schema-decoder": "warn", "effecttsgo/prefer-unsafe-constructor": "warn", - "effecttsgo/process-env": "warn", - "effecttsgo/process-env-in-effect": "warn", "effecttsgo/promise-in-effect-success": "warn", "effecttsgo/provide-layer-succeed-to-provide-service": "warn", "effecttsgo/race-first-with-sleep-to-timeout": "warn", @@ -95,7 +75,6 @@ "effecttsgo/schema-number": "warn", "effecttsgo/schema-opaque-instance-member": "error", "effecttsgo/schema-struct-with-tag": "warn", - "effecttsgo/schema-sync": "warn", "effecttsgo/schema-sync-in-effect": "warn", "effecttsgo/scope-in-layer-effect": "warn", "effecttsgo/sync-to-succeed": "warn", diff --git a/oxlint-presets/strict.json b/oxlint-presets/strict.json new file mode 100644 index 00000000..8ce88c77 --- /dev/null +++ b/oxlint-presets/strict.json @@ -0,0 +1,90 @@ +{ + "options": { + "typeAware": true + }, + "plugins": [ + "effecttsgo" + ], + "rules": { + "effecttsgo/abort-controller-in-effect": "error", + "effecttsgo/acquire-release-disposable": "error", + "effecttsgo/all-of-map-to-for-each": "error", + "effecttsgo/catch-all-tag-dispatch-to-catch-tag": "error", + "effecttsgo/catch-all-to-map-error": "error", + "effecttsgo/catch-chain-to-first-success-of": "error", + "effecttsgo/catch-conditional-refail-to-catch-if": "error", + "effecttsgo/catch-die-to-or-die": "error", + "effecttsgo/catch-if-tag-to-catch-tag": "error", + "effecttsgo/catch-refail-to-tap-error": "error", + "effecttsgo/catch-tag-to-catch-reason": "error", + "effecttsgo/catch-to-ignore": "error", + "effecttsgo/catch-to-or-else-succeed": "error", + "effecttsgo/catch-unfailable-effect": "error", + "effecttsgo/class-self-mismatch": "error", + "effecttsgo/duplicate-package": "error", + "effecttsgo/effect-fn-iife": "error", + "effecttsgo/effect-fn-implicit-any": "error", + "effecttsgo/effect-fn-opportunity": "error", + "effecttsgo/effect-gen-uses-adapter": "error", + "effecttsgo/effect-in-failure": "error", + "effecttsgo/effect-in-void-success": "error", + "effecttsgo/effect-map-flatten": "error", + "effecttsgo/effect-map-void": "error", + "effecttsgo/effect-succeed-with-void": "error", + "effecttsgo/flat-map-conditional-to-filter-or-fail": "error", + "effecttsgo/flat-map-ignored-param-to-and-then": "error", + "effecttsgo/flat-map-to-map": "error", + "effecttsgo/floating-effect": "error", + "effecttsgo/floating-effect-in-vitest": "error", + "effecttsgo/generic-effect-services": "error", + "effecttsgo/global-error-in-effect-catch": "error", + "effecttsgo/global-error-in-effect-failure": "error", + "effecttsgo/layer-merge-all-with-dependencies": "error", + "effecttsgo/lazy-effect": "error", + "effecttsgo/lazy-promise-in-effect-sync": "error", + "effecttsgo/leaking-requirements": "error", + "effecttsgo/map-some-to-as-some": "error", + "effecttsgo/match-effect-to-map-both": "error", + "effecttsgo/match-effect-to-match": "error", + "effecttsgo/missing-effect-context": "error", + "effecttsgo/missing-effect-error": "error", + "effecttsgo/missing-layer-context": "error", + "effecttsgo/missing-return-yield-star": "error", + "effecttsgo/missing-star-in-yield-effect-gen": "error", + "effecttsgo/multiple-catch-tag": "error", + "effecttsgo/multiple-effect-provide": "error", + "effecttsgo/non-object-effect-service-type": "error", + "effecttsgo/obsolete-match-import": "error", + "effecttsgo/obsolete-schema-import": "error", + "effecttsgo/option-match-to-from-option": "error", + "effecttsgo/outdated-api": "error", + "effecttsgo/overridden-schema-constructor": "error", + "effecttsgo/prefer-succeed-some-or-none": "error", + "effecttsgo/prefer-typed-schema-decoder": "error", + "effecttsgo/prefer-unsafe-constructor": "error", + "effecttsgo/promise-in-effect-success": "error", + "effecttsgo/provide-layer-succeed-to-provide-service": "error", + "effecttsgo/race-first-with-sleep-to-timeout": "error", + "effecttsgo/redundant-map-error": "error", + "effecttsgo/redundant-or-die": "error", + "effecttsgo/redundant-schema-tag-identifier": "error", + "effecttsgo/return-effect-in-gen": "error", + "effecttsgo/run-effect-inside-effect": "error", + "effecttsgo/run-of-exit-to-run-exit": "error", + "effecttsgo/schema-literal-non-finite": "error", + "effecttsgo/schema-number": "error", + "effecttsgo/schema-opaque-instance-member": "error", + "effecttsgo/schema-struct-with-tag": "error", + "effecttsgo/schema-sync-in-effect": "error", + "effecttsgo/scope-in-layer-effect": "error", + "effecttsgo/sync-to-succeed": "error", + "effecttsgo/timeout-catch-tag-to-timeout-or-else": "error", + "effecttsgo/try-catch-in-effect-gen": "error", + "effecttsgo/unknown-in-effect-catch": "error", + "effecttsgo/unnecessary-effect-gen": "error", + "effecttsgo/unnecessary-fail-yieldable-error": "error", + "effecttsgo/unnecessary-pipe": "error", + "effecttsgo/unnecessary-pipe-chain": "error", + "effecttsgo/unnecessary-typeof-type": "error" + } +} diff --git a/testdata/tests/oxlint/.oxlintrc-recommended-override.json b/testdata/tests/oxlint/.oxlintrc-recommended-override.json index b80c8e13..a06a958d 100644 --- a/testdata/tests/oxlint/.oxlintrc-recommended-override.json +++ b/testdata/tests/oxlint/.oxlintrc-recommended-override.json @@ -1,6 +1,6 @@ { "extends": ["../../../oxlint-presets/recommended.json"], "rules": { - "effecttsgo/global-date": "off" + "effecttsgo/floating-effect": "off" } } diff --git a/testdata/tests/oxlint/global-date.ts b/testdata/tests/oxlint/global-date.ts deleted file mode 100644 index 4c890ca3..00000000 --- a/testdata/tests/oxlint/global-date.ts +++ /dev/null @@ -1 +0,0 @@ -export const now = Date.now() diff --git a/testdata/tests/oxlint/smoke.mjs b/testdata/tests/oxlint/smoke.mjs index b6a1f412..6b44defc 100644 --- a/testdata/tests/oxlint/smoke.mjs +++ b/testdata/tests/oxlint/smoke.mjs @@ -27,9 +27,12 @@ const run = (...args) => spawnSync(process.execPath, [oxlint, ...args], { const packagePreset = spawnSync(process.execPath, [ "--input-type=module", "--eval", - `import { recommended } from "@effect/tsgo/oxlint-presets"; + `import { recommended, strict } from "@effect/tsgo/oxlint-presets"; import recommendedJson from "@effect/tsgo/oxlint-presets/recommended.json" with { type: "json" }; - if (recommended.rules["effecttsgo/global-date"] !== "warn" || recommendedJson.rules["effecttsgo/global-date"] !== "warn") process.exit(1);` + import strictJson from "@effect/tsgo/oxlint-presets/strict.json" with { type: "json" }; + if (recommended.rules["effecttsgo/floating-effect"] !== "error" || recommendedJson.rules["effecttsgo/floating-effect"] !== "error") process.exit(1); + if (recommended.rules["effecttsgo/global-date"] !== undefined || recommendedJson.rules["effecttsgo/global-date"] !== undefined) process.exit(1); + if (strict.rules["effecttsgo/catch-unfailable-effect"] !== "error" || strictJson.rules["effecttsgo/catch-unfailable-effect"] !== "error") process.exit(1);` ], { cwd: packageDirectory, encoding: "utf8" @@ -62,12 +65,12 @@ const disabled = run("--type-aware", "--config", ".oxlintrc.json", "disabled.ts" assert.equal(disabled.status, 0, disabled.stderr) assert.doesNotMatch(`${disabled.stdout}\n${disabled.stderr}`, /effecttsgo\(floating-effect\)/) -const recommended = run("--config", ".oxlintrc-recommended.json", "global-date.ts") -assert.equal(recommended.status, 0, recommended.stderr) -assert.match(`${recommended.stdout}\n${recommended.stderr}`, /effecttsgo\(global-date\)/) +const recommended = run("--config", ".oxlintrc-recommended.json", "diagnostic.ts") +assert.equal(recommended.status, 1, recommended.stderr) +assert.match(`${recommended.stdout}\n${recommended.stderr}`, /effecttsgo\(floating-effect\)/) -const recommendedOverride = run("--config", ".oxlintrc-recommended-override.json", "global-date.ts") +const recommendedOverride = run("--config", ".oxlintrc-recommended-override.json", "diagnostic.ts") assert.equal(recommendedOverride.status, 0, recommendedOverride.stderr) -assert.doesNotMatch(`${recommendedOverride.stdout}\n${recommendedOverride.stderr}`, /effecttsgo\(global-date\)/) +assert.doesNotMatch(`${recommendedOverride.stdout}\n${recommendedOverride.stderr}`, /effecttsgo\(floating-effect\)/) console.log("Oxlint profile smoke test passed")