Skip to content

feat(eslint-plugin): [no-confusing-void-expression] add ignoreVoidInVoid option to void in void situation #8632

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Conversation

developer-bandi
Copy link
Contributor

PR Checklist

Overview

add ignoreVoidInVoid option in no-confusing-void-expression to resolve void in void situation. below example is avaliable

function returnsVoid(): void {}

// type is correct with void, so not evoke error
function test1(): void {
  return returnsVoid();
}

// type is correct with void, so not evoke error
const test2 = (): void => returnsVoid();

// type is correct with void, so not evoke error
const test3 = (): void => {
  return returnsVoid();
};

@typescript-eslint
Copy link
Contributor

Thanks for the PR, @developer-bandi!

typescript-eslint is a 100% community driven project, and we are incredibly grateful that you are contributing to that community.

The core maintainers work on this in their personal time, so please understand that it may not be possible for them to review your work immediately.

Thanks again!


🙏 Please, if you or your company is finding typescript-eslint valuable, help us sustain the project by sponsoring it transparently on https://opencollective.com/typescript-eslint.

Copy link

netlify bot commented Mar 9, 2024

Deploy Preview for typescript-eslint ready!

Name Link
🔨 Latest commit edbfc36
🔍 Latest deploy log https://app.netlify.com/sites/typescript-eslint/deploys/665c6ac40612ea000813e1d8
😎 Deploy Preview https://deploy-preview-8632--typescript-eslint.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 99 (🟢 up 1 from production)
Accessibility: 100 (no change from production)
Best Practices: 92 (no change from production)
SEO: 98 (no change from production)
PWA: 80 (no change from production)
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify site configuration.

Copy link
Member

@auvred auvred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A nice start, thank you for sending it in!

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label Mar 9, 2024
@developer-bandi
Copy link
Contributor Author

developer-bandi commented Mar 10, 2024

Except for the comment left at the top, there were no objections to the comments you left, so i has reflected all of them.

Copy link
Member

@auvred auvred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Getting closer 🙌

I'm requesting changes on one blocking bug and two non-blocking style/refactor changes!

Comment on lines 123 to 131
if (options.ignoreVoidInVoid) {
if (
invalidAncestor.returnType?.typeAnnotation.type ===
AST_NODE_TYPES.TSVoidKeyword
) {
return;
}
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (options.ignoreVoidInVoid) {
if (
invalidAncestor.returnType?.typeAnnotation.type ===
AST_NODE_TYPES.TSVoidKeyword
) {
return;
}
}
if (options.ignoreVoidInVoid && invalidAncestor.returnType?.typeAnnotation.type === AST_NODE_TYPES.TSVoidKeyword) {
return;
}

[Style] Totally non-blocking, I just noticed that we can inline these two conditions. Feel free to ignore this, if you find the current implementation more readable.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you i agree this style

Comment on lines 189 to 193
targetNodeAncestorHasVoidReturnType(invalidAncestor, [
AST_NODE_TYPES.FunctionDeclaration,
AST_NODE_TYPES.ArrowFunctionExpression,
AST_NODE_TYPES.FunctionExpression,
])
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Refactor] I see no reason why we would want to use targetNodeAncestorHasVoidReturnType with some other set of targets nodes. So I think we can just put those node types inside the function. WDYT?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agreed that there was no possibility of reuse, so i moved the targets inside the function and changed the function name to be more clear.

Comment on lines 409 to 438
function targetNodeAncestorHasVoidReturnType(
node: TSESTree.Node,
targets: (
| AST_NODE_TYPES.FunctionDeclaration
| AST_NODE_TYPES.ArrowFunctionExpression
| AST_NODE_TYPES.FunctionExpression
)[],
): boolean {
if (!node.parent) {
return false;
}

const filter = targets.filter(target => {
if (target === node.parent.type) {
if (
node.parent.returnType?.typeAnnotation.type ===
AST_NODE_TYPES.TSVoidKeyword
) {
return true;
}
}
return false;
});

if (filter.length > 0) {
return true;
}

return targetNodeAncestorHasVoidReturnType(node.parent, targets);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] This function won't stop until it finds a matching parent, so the following case is false negative

function foo(): void {
  const bar = () => {
    return console.log()
  }
}

(playground)

I think that this function should look +- like this:

  • if !node.parent -> return false
  • if node.parent is one of the FunctionDeclaration, ArrowFunctionExpression, FunctionExpression:
    • if node.parent has void in type annotation -> return true
    • if node.parent hasn't void in type annotation -> return false
  • return targetNodeAncestorHasVoidReturnType(node.parent)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in playground, this case seems false positive. anyway, this is bug, so i add test code and fix targetNodeAncestorHasVoidReturnType function

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

in playground, this case seems false positive

As far as I know:

false negative === the rule should report error, but it doesn't do so
false positive === the rule should not report error, but it does so

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not familiar with the testing, so I think I used the terminology the other way around. thank you for telling me!

@developer-bandi developer-bandi requested a review from auvred March 10, 2024 13:33
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Mar 10, 2024
Copy link
Member

@auvred auvred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Progress 🚀

Comment on lines 186 to 192
if (options.ignoreVoidInVoid) {
if (
targetNodeFunctionAncestorNodeHasVoidReturnType(invalidAncestor)
) {
return;
}
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Style] Same as here: #8632 (comment)

Suggested change
if (options.ignoreVoidInVoid) {
if (
targetNodeFunctionAncestorNodeHasVoidReturnType(invalidAncestor)
) {
return;
}
}
if (options.ignoreVoidInVoid && targetNodeFunctionAncestorNodeHasVoidReturnType(invalidAncestor)) {
return;
}

WDYT about inlining this if statement?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same convention is make sense. thank you!

Comment on lines 421 to 425
if (
targets[0] === node.parent.type ||
targets[1] === node.parent.type ||
targets[2] === node.parent.type
) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (
targets[0] === node.parent.type ||
targets[1] === node.parent.type ||
targets[2] === node.parent.type
) {
if (
AST_NODE_TYPES.FunctionDeclaration === node.parent.type ||
AST_NODE_TYPES.ArrowFunctionExpression === node.parent.type ||
AST_NODE_TYPES.FunctionExpression === node.parent.type
) {

[Refactor] Why do we need the targets tuple if we don't iterate over it? IMO if we directly compare node.parent.type to AST node types, it will be more readable. What do you think?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comparing variables directly seems much more readable!

Comment on lines 426 to 432
if (
node.parent.returnType?.typeAnnotation.type ===
AST_NODE_TYPES.TSVoidKeyword
) {
return true;
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] I just noticed that we determine whether function signature has void return type or not by doing pure syntax check.

Thus, the following case is false positive (the rule shouldn't report error, since function returns Foo - an alias of void type):

type Foo = void

function test(): Foo {
  return console.log()
}

playground

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't even think about it. Thank you!

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Testing] Some more cases to add:

  • the case mentioned here - return console.log('foo') on the top-level
  • function without void in signature contains in its body function with void in signature, and this nested function returns return console.log('foo')
  • Type aliases, like in the comment above ^ (in the signature of the parent function, in the signature of the returned function, in both signatures at the same time)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added a test case, but I may have misunderstood the last comment, so please check my new test code. thank you!

Copy link
Member

@auvred auvred Mar 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it's fine.

Also, I think it would be nice to add the following cases:

  • function test(): void & void {
      return console.log('foo')
    }
  • type Foo = void
    declare function foo(): Foo
    function test(): Foo {
      return foo()
    }
  • // se UPD below. there is a typo
    type Foo = void
    const test: Foo = () => console.log('err')

UPD: there was a typo in the last code block! We shouldn't consider case with such a weird typing (the function is being assigned to variable with void type). Instead, I meant the following code:

  • type Foo = void
    const test = (): Foo => console.log('err')

Copy link
Contributor Author

@developer-bandi developer-bandi Mar 11, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you! add all test case and last case has ignoreArrowShorthand option to pass, so add ignoreArrowShorthand option

i think last case situation is same to below comment

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label Mar 11, 2024
@developer-bandi developer-bandi requested a review from auvred March 11, 2024 14:07
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Mar 11, 2024
Comment on lines 123 to 130
if (
options.ignoreVoidInVoid &&
invalidAncestor.returnType?.typeAnnotation.type ===
AST_NODE_TYPES.TSVoidKeyword
) {
return;
}

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] Same as here #8632 (comment)...

type Foo = void
const test = (): Foo => console.log()

The error shouldn't be reported, since arrow function has the void return type

playground

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this error is because of ignoreArrowShorthand. so ignoreArrowShorthand option to true or change code like:

type Foo = void
const test = (): Foo => { return console.log(); }

I add test case with ignoreArrowShorthand option true.

playground

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i think this error is because of ignoreArrowShorthand. so ignoreArrowShorthand option to true or change code like:

The rule reports error because it does pure syntax check instead of type-level check.

type Foo = void
const test1 = (): void => console.log() // not reported - ok
const test2 = (): Foo => console.log() // reported - ok

playground

ignoreArrowShorthand ignores all arrow functions, but rule users may want to ignore only void <-> void cases as mentioned in the original issue.

Copy link
Contributor Author

@developer-bandi developer-bandi Mar 12, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thank you i understand it! and since it seems to be valid even when declaring a type in a variable of a function expression, I modified the code and test case.

type Foo = () => void;
const test: Foo = function () {
  return console.log('err');
};

Comment on lines 416 to 425
if (
node.parent.returnType &&
!tsutils.isTypeFlagSet(
getConstrainedTypeAtLocation(services, node.parent.returnType),
ts.TypeFlags.VoidLike,
)
) {
return true;
}
return false;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (
node.parent.returnType &&
!tsutils.isTypeFlagSet(
getConstrainedTypeAtLocation(services, node.parent.returnType),
ts.TypeFlags.VoidLike,
)
) {
return true;
}
return false;
return (
node.parent.returnType &&
!tsutils.isTypeFlagSet(
getConstrainedTypeAtLocation(services, node.parent.returnType),
ts.TypeFlags.VoidLike,
)
)

[Refactor] This code may be converted into a single return statement. What do you think?

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label Mar 11, 2024
@developer-bandi developer-bandi requested a review from auvred March 11, 2024 16:01
@developer-bandi developer-bandi requested a review from auvred April 13, 2024 14:10
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Apr 13, 2024
Comment on lines 436 to 439
node.returnType.typeAnnotation.type !==
AST_NODE_TYPES.TSAnyKeyword &&
node.returnType.typeAnnotation.type !==
AST_NODE_TYPES.TSUnknownKeyword) ||
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] I somehow missed this in previous reviews: we can't rely on syntactic check here

(): any => console.log(); // reported - nice!
(): unknown => console.log(); // reported - nice!

type Foo = any
(): Foo => console.log(); // not reported - bug!
type Bar = unknown
(): Bar => console.log(); // not reported - bug!

playground

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When specifying a type using a type alias, it is difficult to find a way to determine what type it is.

Could you please give me a hint?

type Bar = unknown
(): Bar => console.log();  // how to know Bar is unknown type in create fn

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@auvred Can you help with the issue left in the comment above?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not 100% sure about this, but I'd try playing around with function signatures. There is nice utility function in the ts-api-utils package - getCallSignaturesOfType.

See: https://github.com/JoshuaKGoldberg/ts-api-utils/blob/d808eba09baa3ba093cd31b85aad57bdcc539867/src/types/getters.ts#L23

Search with some examples of usage: https://github.com/search?q=repo%3Atypescript-eslint%2Ftypescript-eslint%20getCallSignaturesOfType&type=code

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it works to me very well. thank you!

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label Apr 16, 2024
Copy link
Member

@auvred auvred Apr 20, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Testing] I think it would be nice to add few tests after #8809 is merged. This PR fixes the ancestorHasReturnType behavior.

We can add something like this:

function foo(): void {
  () => () => console.log();
}

function foo(): any {
  () => () => console.log();
}

(both of the above cases should be reported)

Copy link

codecov bot commented Apr 28, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 87.24%. Comparing base (216d1b0) to head (8a7a8e7).
Report is 105 commits behind head on main.

Current head 8a7a8e7 differs from pull request most recent head edbfc36

Please upload reports for the commit edbfc36 to get more accurate results.

Additional details and impacted files
@@            Coverage Diff             @@
##             main    #8632      +/-   ##
==========================================
- Coverage   87.40%   87.24%   -0.16%     
==========================================
  Files         260      251       -9     
  Lines       12605    12336     -269     
  Branches     3937     3894      -43     
==========================================
- Hits        11017    10763     -254     
+ Misses       1313     1303      -10     
+ Partials      275      270       -5     
Flag Coverage Δ
unittest 87.24% <100.00%> (-0.16%) ⬇️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files Coverage Δ
...t-plugin/src/rules/no-confusing-void-expression.ts 100.00% <100.00%> (ø)

... and 61 files with indirect coverage changes

@developer-bandi developer-bandi requested a review from auvred April 28, 2024 11:18
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label Apr 28, 2024
Copy link
Member

@auvred auvred left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Requesting changes on #8632 (comment). I will re-review this PR more thoroughly after this comment is resolved.

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label May 1, 2024
@developer-bandi developer-bandi requested a review from auvred May 3, 2024 15:52
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label May 3, 2024
@@ -376,5 +427,55 @@ export default createRule<Options, MessageId>({
const type = getConstrainedTypeAtLocation(services, targetNode);
return tsutils.isTypeFlagSet(type, ts.TypeFlags.VoidLike);
}

function hasValidReturnType(
services: ParserServicesWithTypeInformation,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Refactor] (optional) hasValidReturnType function is declared in block where context is visible. What do you think about calling const services = getParserServices(context); inside hasValidReturnType (like canFix function above does) instead of passing services as an argument?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I too believe that using context is better than passing a service.

Comment on lines 442 to 447
const functionType =
(ts.isFunctionExpression(functionTSNode) ||
ts.isArrowFunction(functionTSNode)
? getContextualType(checker, functionTSNode)
: services.getTypeAtLocation(node)) ??
services.getTypeAtLocation(node);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] Why do we get a contextual type for function expressions and arrow functions?

The rule you took this code from has a detailed explanation of why it calls getContextualType for function expressions and arrow functions:

// function expressions will not have their return type modified based on receiver typing
// so we have to use the contextual typing in these cases, i.e.
// const foo1: () => Set<string> = () => new Set<any>();
// the return type of the arrow function is Set<any> even though the variable is typed as Set<string>
let functionType =
ts.isFunctionExpression(functionTSNode) ||
ts.isArrowFunction(functionTSNode)
? getContextualType(checker, functionTSNode)
: services.getTypeAtLocation(functionNode);

This means that the following case is reported:

const test: () => any  = (): void => console.log()

(it should not be reported since (): void => console.log() is allowed with ignoreVoidInVoid: true)

playground

Comment on lines 450 to 453
for (const signature of tsutils.getCallSignaturesOfType(
functionType,
)) {
return !(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[Bug] This code returns from for loop on the first iteration. So if a function has several signatures, the rule will check only the first signature:

function test1(): void
function test1(arg: string): any
function test1(arg?: string): any | void {
  if (arg) {
    return arg
  }

  return console.log() // not reported - ok
}

function test2(arg: string): any
function test2(): void
function test2(arg?: string): any | void {
  if (arg) {
    return arg
  }

  return console.log() // reported - bug
}

playground

Comment on lines 454 to 461
tsutils.isTypeFlagSet(
signature.getReturnType(),
ts.TypeFlags.Any,
) ||
tsutils.isTypeFlagSet(
signature.getReturnType(),
ts.TypeFlags.Unknown,
)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
tsutils.isTypeFlagSet(
signature.getReturnType(),
ts.TypeFlags.Any,
) ||
tsutils.isTypeFlagSet(
signature.getReturnType(),
ts.TypeFlags.Unknown,
)
tsutils.isTypeFlagSet(
signature.getReturnType(),
ts.TypeFlags.Any | ts.TypeFlags.Unknown,
)

[Refactor] Let's do a bitwise OR of these flags instead of calling isTypeFlagSet twice?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not understand that the isTypeFlagSet function utilizes bitwise operators, so I called it twice. thank you for telling me!

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label May 4, 2024
@developer-bandi developer-bandi requested a review from auvred May 5, 2024 10:26
@github-actions github-actions bot removed the awaiting response Issues waiting for a reply from the OP or another party label May 5, 2024
Comment on lines +450 to +459
if (
((node.type === AST_NODE_TYPES.FunctionExpression ||
node.type === AST_NODE_TYPES.ArrowFunctionExpression) &&
isValidFunctionExpressionReturnType(node, {
allowTypedFunctionExpressions: true,
})) ||
ancestorHasReturnType(node)
) {
return true;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we check that function signature don't contain any and unknown in the return type, we should do the same for typed functions/variable declarations. The following cases are not reported:

type HigherOrderType = () => any;
(): HigherOrderType => () => console.log();

const x: HigherOrderType = () => console.log();

playground

return arg;
}

return console.log(); // reported - bug
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please see my comment from the one of the previous reviews #8632 (comment). 🙂

The code samples I provide, which contain some error messages, are for reference only. They are not intended to be directly copied into real tests. I'm including comments on these code samples to better illustrate what the bug I found is. The same goes for variable names: test1 and test2 are named to avoid name collisions in the playground. Having the test2 function in a separate test case without test1 looks a bit confusing.

I ask you to pay attention to the reviews and not just blindly insert some stuff into the code, assuming that someone will notice it in one of the next reviews anyway.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologize for making the same mistake by not checking the code thoroughly after writing it.

In the future, I will look more closely before submitting a review.

@auvred auvred added the awaiting response Issues waiting for a reply from the OP or another party label May 11, 2024
@bradzacher bradzacher added the enhancement: plugin rule option New rule option for an existing eslint-plugin rule label May 28, 2024
@developer-bandi
Copy link
Contributor Author

I tried several methods to correct the error, but it seems that I need to check if the return type is void, rather than excluding the current any and unknown types.

However, there is currently not much time to work on this, so only the test code in this PR has been modified and reflected. If there is anyone who wants to continue this work or it has been too long, you can close the pr or provide a new pr.

@JoshuaKGoldberg
Copy link
Member

👍 thanks for the heads up, and of course all your work on this @developer-bandi!

Closing the PR out now so if other folks want to tackle it, they can. If anybody wants to drive it forward, please do post your own PR - and if you use this as a start, consider adding Co-authored-by: @developer-bandi at the end of your PR description. Thanks! 😊

@github-actions github-actions bot locked as resolved and limited conversation to collaborators Jun 10, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
awaiting response Issues waiting for a reply from the OP or another party enhancement: plugin rule option New rule option for an existing eslint-plugin rule
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Enhancement: [no-confusing-void-expression] Add option to ignore void<->void
5 participants