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
feat(eslint-plugin): add strict-type-predicates #738
feat(eslint-plugin): add strict-type-predicates #738
Conversation
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 haven't read the code yet, but I noticed a few things:
- you forgot a rule readme
- there needs to be an entry in the root eslint-plugin readme
- there should be an entry in
configs/all.json
run:
yarn generate:configs
to add it toall.json
yarn check:docs
to validate your docs setup
Hey @bradzacher any more thoughts on this? I'm thinking that I'll use a local fork for my own project to give it some real-world testing. Are there any guides on how to do this (or is it as simple as |
It's on my todo list. I've had family visiting for a few weeks so my spare time has been sparse. I'm about to start getting back into the review queue. I've never done a local fork, sorry, so I don't have any advice for you. I'd assume it should be pretty simple because the publish step is literally just publish each package's directory. |
Looks like it has some intersection with const number = 1;
if (number === null) {}
if (number === undefined) {} It doesn't handle that though: const numberOrNull: number | null = 0;
if (numberOrNull != null) {}
const number: number = 0;
if (typeof number === 'string') {} I wonder if there's really a need for multiple rules being so similar (conceptually)? |
I like the idea of merging it into no-unnecessary-condition, that seems like it could be a good fit. I think the name is an improvement too. I feel like it might need some options to enable the new behaviour though, as it's quite strict and might be undesirable for some people. |
I'm curious - how much value is there for you in these checks? const numberOrNull: number | null = 0;
// Implicitly checks for `!== undefined`, which is always false.
if (numberOrNull != null) {
return;
}
const numberOrUndefined: number | undefined = 0;
// Implicitly checks for `!== null`, which is always false.
if (numberOrNull != undefined) {
return;
} I've always held the opinion that null checks should always be made This does seem like it has value for obvious reasons: const number: number = 0;
// Always false.
if (typeof number === 'string') {
return;
} |
In general I've found the rule really valuable for killing stale code, increasing confidence in refactors, and readability (you know exactly what types are being dealt with). The trade-off of having more verbosity in exchange for more clarity has been one we've been happy with. Specifically regarding null/undefined comparisons, they're good for refactors where a null might be swapped for an undefined, but a What rule do you use to enforce that? |
The base eslint I'd be happy to have both in
|
f68b732
to
97fa253
Compare
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.
thanks for being patient.
a few comments about the place
mostly looks good.
thanks for doing this.
I think it'd be great if the (==|!=|===|!===) (null|undefined)
checks had a flag to disable it.
It might just be me being selfish, because I like everything but that check :)
const number: number = 0; | ||
// Always false. | ||
if (typeof number === 'number') { | ||
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.
Wouldn't this be reported by the rule, because it's always true?
This should probably follow the above tests
const numberOrUndefined: number | undefined = 0;
const numberOrNull: number | null = 0; | ||
if (numberOrNull !== null) { | ||
return; | ||
} | ||
|
||
const numberOrUndefined: number | undefined = 0; | ||
if (numberOrNull !== undefined) { | ||
return; | ||
} | ||
|
||
const number: number = 0; | ||
// Always false. | ||
if (typeof number === 'number') { | ||
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.
nit with these - just for correctness, typescript narrows the type of the const
variables to just be number
, because it knows they can't change, so they can't be undefined
.
A good way around this is to either declare
the variables:
declare const numberOrUndefined: number | undefined;
if (numberOrNull !== undefined) {
return;
}
Or wrap your examples in a function:
function ex1(numberOrUndefined: number | undefined) {
if (numberOrNull !== undefined) {
return;
}
}
type Options = [ | ||
{ | ||
typesToIgnore?: string[]; | ||
}, | ||
]; |
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.
It looks like you removed this option from the rule
type Options = [];
}, | ||
schema: [], | ||
}, | ||
defaultOptions: [{}], |
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.
nit
defaultOptions: [{}], | |
defaultOptions: [], |
return isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks') | ||
? { | ||
BinaryExpression(node: TSESTree.BinaryExpression): void { | ||
const equals = getEqualsKind(node.operator); | ||
if (equals !== undefined) { | ||
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); | ||
checkEquals(tsNode as ts.BinaryExpression, node, equals); | ||
} | ||
}, | ||
} | ||
: // TODO: emit warning that strictNullChecks is required | ||
{}; |
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.
Do we need to disable the whole rule if strictNullChecks
isn't enabled?
We could probably just disable the null/undefined checks instead.
const equals = getEqualsKind(node.operator); | ||
if (equals !== undefined) { | ||
const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); | ||
checkEquals(tsNode as ts.BinaryExpression, node, equals); |
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 should be done automatically now
checkEquals(tsNode as ts.BinaryExpression, node, equals); | |
checkEquals(tsNode, node, equals); |
return !anyOther | ||
? true | ||
: anyNull && anyUndefined | ||
? undefined | ||
: anyNull | ||
? 'null' | ||
: anyUndefined | ||
? 'undefined' | ||
: false; |
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.
nit - in cases like this I find that a series of if statements are a lot easier to read
// get<number | null>() === null; | ||
// get<number | undefined>() === undefined; | ||
// get<string | null | undefined>() == null; | ||
// get<string | null | undefined>() != undefined; |
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.
uncomment or delete
{ | ||
code: ` | ||
declare function get<T>(): T; | ||
enum E {} | ||
typeof get<E>() === "number";`, | ||
errors: [ | ||
{ | ||
messageId: 'expressionAlwaysTrue', | ||
line: 4, | ||
column: 1, | ||
}, | ||
], | ||
}, |
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.
for completeness, would be good to get a string enum test enum E { a = 'aaa' }
.
], | ||
}, | ||
|
||
// typeof symbol |
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.
just cos you've got a theme going, would be good to add a test for Symbol
.
Codecov Report
@@ Coverage Diff @@
## master #738 +/- ##
==========================================
- Coverage 95.55% 95.53% -0.03%
==========================================
Files 145 146 +1
Lines 6576 6691 +115
Branches 1878 1917 +39
==========================================
+ Hits 6284 6392 +108
- Misses 111 112 +1
- Partials 181 187 +6
|
Would really love to see this, one of the last rules that still requires me to use TSLint! |
So how would this rule relate to |
#1659 will deal with the null/undefined case (which I was asking to be behind an option in here anyway) But |
This is a direct port of the TSLint version, with as few changes as possible.
Notable differences:
strictNullChecks
is not enabled.unknown
,strictNullChecks
,noStrictNullChecks
)