From f565598a535b5d829a95c8e8456ec4fff676054b Mon Sep 17 00:00:00 2001 From: camchenry <1514176+camchenry@users.noreply.github.com> Date: Wed, 12 Nov 2025 17:17:03 +0000 Subject: [PATCH] feat: add options schema for `return-await` (#414) --- internal/rules/return_await/options.go | 67 ++++++++++++++ internal/rules/return_await/return_await.go | 35 ++------ .../rules/return_await/return_await_test.go | 89 +++++++++---------- internal/rules/return_await/schema.json | 21 +++++ 4 files changed, 140 insertions(+), 72 deletions(-) create mode 100644 internal/rules/return_await/options.go create mode 100644 internal/rules/return_await/schema.json diff --git a/internal/rules/return_await/options.go b/internal/rules/return_await/options.go new file mode 100644 index 00000000..dd91ccac --- /dev/null +++ b/internal/rules/return_await/options.go @@ -0,0 +1,67 @@ +// Code generated by github.com/atombender/go-jsonschema, DO NOT EDIT. + +package return_await + +import "encoding/json" +import "fmt" +import "reflect" + +type ReturnAwaitOptions struct { + // Configures when to require or disallow returning awaited values: 'always' + // requires await, 'never' disallows it, 'in-try-catch' requires it in try/catch + // blocks, 'error-handling-correctness-only' requires it only when it affects + // error handling + Option ReturnAwaitOptionsOption `json:"option,omitempty"` +} + +type ReturnAwaitOptionsOption string + +const ReturnAwaitOptionsOptionAlways ReturnAwaitOptionsOption = "always" +const ReturnAwaitOptionsOptionErrorHandlingCorrectnessOnly ReturnAwaitOptionsOption = "error-handling-correctness-only" +const ReturnAwaitOptionsOptionInTryCatch ReturnAwaitOptionsOption = "in-try-catch" +const ReturnAwaitOptionsOptionNever ReturnAwaitOptionsOption = "never" + +var enumValues_ReturnAwaitOptionsOption = []interface{}{ + "always", + "error-handling-correctness-only", + "in-try-catch", + "never", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ReturnAwaitOptionsOption) UnmarshalJSON(value []byte) error { + var v string + if err := json.Unmarshal(value, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_ReturnAwaitOptionsOption { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_ReturnAwaitOptionsOption, v) + } + *j = ReturnAwaitOptionsOption(v) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *ReturnAwaitOptions) UnmarshalJSON(value []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(value, &raw); err != nil { + return err + } + type Plain ReturnAwaitOptions + var plain Plain + if err := json.Unmarshal(value, &plain); err != nil { + return err + } + if v, ok := raw["option"]; !ok || v == nil { + plain.Option = "in-try-catch" + } + *j = ReturnAwaitOptions(plain) + return nil +} diff --git a/internal/rules/return_await/return_await.go b/internal/rules/return_await/return_await.go index bf9a715d..a5b0a6a3 100644 --- a/internal/rules/return_await/return_await.go +++ b/internal/rules/return_await/return_await.go @@ -38,19 +38,6 @@ func buildRequiredPromiseAwaitSuggestionMessage() rule.RuleMessage { } } -type ReturnAwaitOption uint8 - -const ( - ReturnAwaitOptionAlways ReturnAwaitOption = iota - ReturnAwaitOptionErrorHandlingCorrectnessOnly - ReturnAwaitOptionInTryCatch - ReturnAwaitOptionNever -) - -type ReturnAwaitOptions struct { - Option *ReturnAwaitOption -} - type scopeInfo struct { hasAsync bool owningFunc *ast.Node @@ -73,37 +60,31 @@ const ( whetherToAwaitNoAwait ) -func getWhetherToAwait(affectsErrorHandling bool, option ReturnAwaitOption) whetherToAwait { +func getWhetherToAwait(affectsErrorHandling bool, option ReturnAwaitOptionsOption) whetherToAwait { switch option { - case ReturnAwaitOptionAlways: + case ReturnAwaitOptionsOptionAlways: return whetherToAwaitAwait - case ReturnAwaitOptionErrorHandlingCorrectnessOnly: + case ReturnAwaitOptionsOptionErrorHandlingCorrectnessOnly: if affectsErrorHandling { return whetherToAwaitAwait } return whetherToAwaitDontCare - case ReturnAwaitOptionInTryCatch: + case ReturnAwaitOptionsOptionInTryCatch: if affectsErrorHandling { return whetherToAwaitAwait } return whetherToAwaitNoAwait - case ReturnAwaitOptionNever: + case ReturnAwaitOptionsOptionNever: return whetherToAwaitNoAwait default: - panic("unexpected ReturnAwaitOption") + panic("unexpected ReturnAwaitOptionsOption") } } var ReturnAwaitRule = rule.Rule{ Name: "return-await", Run: func(ctx rule.RuleContext, options any) rule.RuleListeners { - opts, ok := options.(ReturnAwaitOptions) - if !ok { - opts = ReturnAwaitOptions{} - } - if opts.Option == nil { - opts.Option = utils.Ref(ReturnAwaitOptionInTryCatch) - } + opts := utils.UnmarshalOptions[ReturnAwaitOptions](options, "return-await") var scope *scopeInfo @@ -259,7 +240,7 @@ var ReturnAwaitRule = rule.Rule{ affectsErrorHandling := affectsExplicitErrorHandling(node) || affectsExplicitResourceManagement(node) useAutoFix := !affectsErrorHandling - shouldAwaitInCurrentContext := getWhetherToAwait(affectsErrorHandling, *opts.Option) + shouldAwaitInCurrentContext := getWhetherToAwait(affectsErrorHandling, opts.Option) switch shouldAwaitInCurrentContext { case whetherToAwaitAwait: diff --git a/internal/rules/return_await/return_await_test.go b/internal/rules/return_await/return_await_test.go index 45aa95cb..4e2ffe04 100644 --- a/internal/rules/return_await/return_await_test.go +++ b/internal/rules/return_await/return_await_test.go @@ -5,7 +5,6 @@ import ( "github.com/typescript-eslint/tsgolint/internal/rule_tester" "github.com/typescript-eslint/tsgolint/internal/rules/fixtures" - "github.com/typescript-eslint/tsgolint/internal/utils" ) func TestReturnAwait(t *testing.T) { @@ -88,7 +87,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionErrorHandlingCorrectnessOnly)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "error-handling-correctness-only"}`), }, {Code: ` async function test() { @@ -109,7 +108,7 @@ async function test(unknownParam: unknown) { return 1; } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -117,15 +116,15 @@ async function test(unknownParam: unknown) { return 1; } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: "const test = () => 1;", - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: "const test = async () => 1;", - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -133,7 +132,7 @@ async function test(unknownParam: unknown) { return Promise.resolve(1); } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -147,7 +146,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -159,7 +158,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -173,7 +172,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -189,7 +188,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -197,11 +196,11 @@ async function test(unknownParam: unknown) { return Promise.resolve(1); } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionNever)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "never"}`), }, { Code: "const test = async () => Promise.resolve(1);", - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionNever)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "never"}`), }, { Code: ` @@ -215,7 +214,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionNever)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "never"}`), }, { Code: ` @@ -223,11 +222,11 @@ async function test(unknownParam: unknown) { return await Promise.resolve(1); } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), }, { Code: "const test = async () => await Promise.resolve(1);", - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), }, { Code: ` @@ -241,7 +240,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), }, { Code: ` @@ -255,7 +254,7 @@ async function test(unknownParam: unknown) { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), }, { Code: ` @@ -372,7 +371,7 @@ async function f() { using something = bleh; } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), }, { Code: ` @@ -387,7 +386,7 @@ async function returnAwait() { return await asyncFn(); } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -404,7 +403,7 @@ async function outerFunction() { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -419,7 +418,7 @@ async function outerFunction() { const innerFunction = async () => asyncFn(); } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), }, { Code: ` @@ -587,7 +586,7 @@ class C { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionErrorHandlingCorrectnessOnly)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "error-handling-correctness-only"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -643,7 +642,7 @@ class C { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -699,7 +698,7 @@ class C { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -774,7 +773,7 @@ class C { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "nonPromiseAwait", @@ -785,7 +784,7 @@ class C { { Code: "const test = async () => await 1;", Output: []string{"const test = async () => 1;"}, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "nonPromiseAwait", @@ -796,7 +795,7 @@ class C { { Code: "const test = async () => await Promise.resolve(1);", Output: []string{"const test = async () => Promise.resolve(1);"}, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "disallowedPromiseAwait", @@ -816,7 +815,7 @@ class C { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "disallowedPromiseAwait", @@ -836,7 +835,7 @@ class C { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionNever)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "never"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "nonPromiseAwait", @@ -856,7 +855,7 @@ class C { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionNever)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "never"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "disallowedPromiseAwait", @@ -912,7 +911,7 @@ class C { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionNever)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "never"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "disallowedPromiseAwait", @@ -932,7 +931,7 @@ class C { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "nonPromiseAwait", @@ -952,7 +951,7 @@ class C { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -963,7 +962,7 @@ class C { { Code: "const test = async () => Promise.resolve(1);", Output: []string{"const test = async () => await Promise.resolve(1);"}, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -991,7 +990,7 @@ async function buzz() { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1029,7 +1028,7 @@ async function buzz() { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1065,7 +1064,7 @@ async function buzz() { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1091,7 +1090,7 @@ async function baz() {} const buzz = async () => ((await foo()) ? await bar() : await baz()); `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1115,7 +1114,7 @@ async function bar() {} const buzz = async () => ((await foo()) ? 1 : await bar()); `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1549,7 +1548,7 @@ async function f() { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1585,7 +1584,7 @@ async function f() { } } `, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1631,7 +1630,7 @@ async function f() { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionAlways)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "always"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "requiredPromiseAwait", @@ -1669,7 +1668,7 @@ async function outerFunction() { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "disallowedPromiseAwait", @@ -1703,7 +1702,7 @@ async function outerFunction() { } `, }, - Options: ReturnAwaitOptions{Option: utils.Ref(ReturnAwaitOptionInTryCatch)}, + Options: rule_tester.OptionsFromJSON[ReturnAwaitOptions](`{"option": "in-try-catch"}`), Errors: []rule_tester.InvalidTestCaseError{ { MessageId: "disallowedPromiseAwait", diff --git a/internal/rules/return_await/schema.json b/internal/rules/return_await/schema.json new file mode 100644 index 00000000..944c9816 --- /dev/null +++ b/internal/rules/return_await/schema.json @@ -0,0 +1,21 @@ +{ + "$schema": "https://json-schema.org/draft-07/schema#", + "definitions": { + "return_await_options": { + "type": "object", + "properties": { + "option": { + "type": "string", + "enum": [ + "always", + "error-handling-correctness-only", + "in-try-catch", + "never" + ], + "default": "in-try-catch", + "description": "Configures when to require or disallow returning awaited values: 'always' requires await, 'never' disallows it, 'in-try-catch' requires it in try/catch blocks, 'error-handling-correctness-only' requires it only when it affects error handling" + } + } + } + } +}