-
-
Notifications
You must be signed in to change notification settings - Fork 2.8k
fix(eslint-plugin): [no-invalid-void-type] don't flag function overloading with void
in return type annotation
#10422
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
fix(eslint-plugin): [no-invalid-void-type] don't flag function overloading with void
in return type annotation
#10422
Conversation
Thanks for the PR, @ronami! 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. |
✅ Deploy Preview for typescript-eslint ready!
To edit notification comments on pull requests, go to your Netlify site configuration. |
View your CI Pipeline Execution ↗ for commit 7887cc9.
☁️ Nx Cloud last updated this comment at |
Codecov ReportAll modified and coverable lines are covered by tests ✅
Additional details and impacted files@@ Coverage Diff @@
## main #10422 +/- ##
==========================================
+ Coverage 87.26% 87.30% +0.03%
==========================================
Files 450 451 +1
Lines 15715 15750 +35
Branches 4601 4615 +14
==========================================
+ Hits 13714 13750 +36
Misses 1645 1645
+ Partials 356 355 -1
Flags with carried forward coverage won't be shown. Click here to find out more.
|
/* istanbul ignore next */ | ||
return []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a preferred way to tackle this? I think this is currently an unreachable line.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#10388 - either it's reachable or it's not.
If it's not actually reachable then I'd say use type assertion(s) to tell TypeScript why it's wrong.
There might be opportunities to suggest improvements to AST types too. Example: the getMembers(node.parent.parent)
later on has the provided type as TSESTree.Node
. But maybe somewhere in there a type could be made more specific? Is TSESTree.ExportDefaultDeclaration
's .parent
always a TSESTree.BlockStatement | TSESTree.Program
, rather than the catch-all TSESTree.Node
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Interesting! I didn't consider proposing changes/improvements to the AST types. I've found #9560, and I think it would be a good option if I could be sure the changes are correct.
To my understanding, TypeScript's AST doesn't even have an actual AST node for ExportDefaultDeclaration
or ExportNamedDeclaration
as they're expressed as modifiers
.
I've looked into @babel/parser
, @swc/types
, and @oxc-project/types
(also the more technical 1, 2), but I didn't find anything that makes me confident this is valid in every case. I did verify @typescript-eslint
itself is type-checked successfully with these assumptions.
The stricter parent types for the AST I'd like to propose:
-
ExportDefaultDeclaration
andExportNamedDeclaration
should have the stricterparent
type ofTSESTree.BlockStatement | TSESTree.Program | TSESTree. TSModuleBlock
(I'm not sure ifTSESTree.BlockStatement
is an option). -
FunctionDeclaration
should have the stricterparent
type ofTSESTree.BlockStatement | TSESTree.ExportDefaultDeclaration | TSESTree.ExportNamedDeclaration | TSESTree.Program
.TypeScript itself doesn't have a specific
parent
forFunctionDeclaration
(it falls back to the genericts.Node
), although they do have stricter parent types for some other nodes.
How did we verify the correctness of the proposals in #6225? Also, would this type of change be considered a breaking change? I noticed that #9560 was released in v8, but it wasn't marked as a breaking change.
I'm a bit hesitant to change the AST's types, as it can potentially lead to bugs, and I would love to hear opinions on this.
I updated the PR with these changes, so it's easier to discuss them.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The stricter parent types for the AST I'd like to propose:
I'd say those proposals should be a separate issue. I'm generally in favor of stricter parent types in general but we should discuss.
So I think how to handle that in this PR is up to you. Maybe either:
- Link to the issue with an
as
- Draft this PR and mark it blocked till that's resolved
How did we verify the correctness of the proposals in #6225?
Other than yarn build
and yarn typecheck
, pretty much just manual knowledge and testing.
breaking change?
I don't think improving the types of the AST to better reflect reality is breaking. In that sense it's a "feature".
Changing the runtime value would IMO be a breaking change.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That sounds good; I'm not sure why I mixed both on the same PR; thanks! 👍
I've opened #10682, drafted this PR, and marked it as blocked.
If the issue is accepted, I'll create a new PR with the stricter parent node changes and eventually re-open this PR for review with only the relevant changes.
const id = nullThrows( | ||
node.id, | ||
'This may be null if and only if the parent is an `ExportDefaultDeclaration`.', | ||
); | ||
|
||
return id.name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This currently only gets called for nodes whose parents aren't ExportDefaultDeclaration
, and so this can never be null
. If this code gets refactored, it may change, and this can throw.
I can change this to a chain-expression, though this will show up as a partially covered line by codecov.
const id = nullThrows( | |
node.id, | |
'This may be null if and only if the parent is an `ExportDefaultDeclaration`.', | |
); | |
return id.name; | |
return node.id?.name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the other comment, my fallback would be to use a !
with a disable comment explaining why the types are wrong. If possible, fixing up the types to be correct would be even better.
You might find the existing uses of the MakeRequired
utils type interesting. Some places in code use for node types in similar situations. E.g. no-confusing-void-expression
has handling for return statements that must have an argument
. Maybe there's a similar flow here for functions that must have an id
?
I'm not worried about refactors. Our test coverage is so high that the cost of unnecessary safety precautions in code is IMO higher than the cost of the brittleness.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm confident the only possibility of node.id
being null
(for FunctionDeclaration
or TSDeclareFunction
) is in cases where the parent node is an ExportDefaultDeclaration
(also according to JavaScript's spec):
typescript-eslint/packages/ast-spec/src/base/FunctionBase.ts
Lines 51 to 58 in d24a828
/** | |
* The function's name. | |
* - For an `ArrowFunctionExpression` this is always `null`. | |
* - For a `FunctionExpression` this may be `null` if the name is omitted. | |
* - For a `FunctionDeclaration` or `TSDeclareFunction` this may be `null` if | |
* and only if the parent is an `ExportDefaultDeclaration`. | |
*/ | |
id: Identifier | null; |
I think that updating the types to be stricter here would be more challenging. I've resorted to using a !
with a disable comment and explaining why this is necessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM overall! Just requesting touchups around the types / istanbul ignore
s. 🚀
/* istanbul ignore next */ | ||
return []; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
#10388 - either it's reachable or it's not.
If it's not actually reachable then I'd say use type assertion(s) to tell TypeScript why it's wrong.
There might be opportunities to suggest improvements to AST types too. Example: the getMembers(node.parent.parent)
later on has the provided type as TSESTree.Node
. But maybe somewhere in there a type could be made more specific? Is TSESTree.ExportDefaultDeclaration
's .parent
always a TSESTree.BlockStatement | TSESTree.Program
, rather than the catch-all TSESTree.Node
?
const id = nullThrows( | ||
node.id, | ||
'This may be null if and only if the parent is an `ExportDefaultDeclaration`.', | ||
); | ||
|
||
return id.name; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Similar to the other comment, my fallback would be to use a !
with a disable comment explaining why the types are wrong. If possible, fixing up the types to be correct would be even better.
You might find the existing uses of the MakeRequired
utils type interesting. Some places in code use for node types in similar situations. E.g. no-confusing-void-expression
has handling for return statements that must have an argument
. Maybe there's a similar flow here for functions that must have an id
?
I'm not worried about refactors. Our test coverage is so high that the cost of unnecessary safety precautions in code is IMO higher than the cost of the brittleness.
{ | ||
code: 'type invalidVoidUnion = void | Map;', | ||
errors: [ | ||
{ | ||
column: 25, | ||
line: 1, | ||
messageId: 'invalidVoidUnionConstituent', | ||
}, | ||
], | ||
}, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test is unrelated to the PR, but it adds coverage to part of the code that is currently uncovered (it seemed small enough to be part of the PR, even if it's unrelated).
Please let me know if I should revert this part.
return getStaticMemberAccessValue(node, context); | ||
} | ||
|
||
function hasOverloadMethods( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this may be useful as a shared utility function for different rules. Re-using this function (pretty much as is) simplifies tackling #4586 (which I'll be happy to push a PR for), for example.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding that this attempts to emulate checker.isImplementationOfOverload()
for rules which aren't type-aware.
The no-invalid-void-type issue is being fixed: typescript-eslint/typescript-eslint#10422
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Commented inline. Exciting! 🚀
I think just #10422 (comment) is left.
Opening for re-review, please note:
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I think this is great as-is. The utility has no known issues as used right now so we can cross that bridge when we get to it IMO. Awesome stuff! 🙌
PR Checklist
void
in return type union for overloaded function #5752Overview
This PR addresses #5752 and adjusts the rule so it doesn't report on
void
that's used as part of the return type of a function overload implementation: