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
Update: treat all literals like boolean literal in no-constant-condition #13245
Conversation
@@ -129,7 +129,7 @@ module.exports = { | |||
const isLeftConstant = isConstant(node.left, inBooleanPosition); | |||
const isRightConstant = isConstant(node.right, inBooleanPosition); | |||
const isLeftShortCircuit = (isLeftConstant && isLogicalIdentity(node.left, node.operator)); | |||
const isRightShortCircuit = (isRightConstant && isLogicalIdentity(node.right, node.operator)); | |||
const isRightShortCircuit = (inBooleanPosition && isRightConstant && isLogicalIdentity(node.right, node.operator)); |
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.
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 is indeed a bug that should be fixed whether or not #13238 gets accepted.
Could we maybe also this:
(
!node.parent ||
node.parent.type !== "BinaryExpression" ||
!(EQUALITY_OPERATORS.includes(node.parent.operator) || RELATIONAL_OPERATORS.includes(node.parent.operator))
)
replace with just inBooleanPosition
?
I think it might fix similar bugs, such as:
/*eslint no-constant-condition: "error"*/
if ((foo || 'bar' || 'bar') === 'bar'); // error, false positive
Though, it seems that the whole condition starting with node.operator === "||" &&
might be removed in this PR (if #13238 gets accepted), but we should also doublecheck what's happening with the recursion in isLogicalIdentity
. It already looks buggy in the actual version:
/*eslint no-constant-condition: "error"*/
if (a && false || b); // error, false positive
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 cause is an inappropriate propagation of short circuit.
fixed in 96d0834
@Zzzen let's wait to see if the original issue will be accepted, before reviewing. If it doesn't get accepted, we could just fix the other bug you found in this PR. |
lib/rules/no-constant-condition.js
Outdated
return (operator === "||" && !!node.value) || | ||
(operator === "&&" && !node.value); |
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.
ESTree doesn't guarantee node.value
is a valid value. For example, if you run ESLint on Node.js that doesn't support BigInt, node.value
is null
at bigint literals. It will cause false positive/negative. Similarly, RegExp literals that contain new syntax, bigdecimal literals (stage 1) are, too.
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.
How about reporting an unsupported error like the following code?
class UnsupportedNodeError extends Error {}
function isNodeTruthy(node) {
if (node.value === null && node.raw !== 'null') {
throw new UnsupportedNodeError(`unsupported node: ${node.type}`)
}
return !!node.value;
}
function isLogicalIdentity(node, operator) {
switch (node.type) {
case "Literal":
return (operator === "||" && isNodeTruthy(node)) ||
(operator === "&&" && !isNodeTruthy(node));
// ...
}
}
function reportIfConstant(node) {
try {
if (node.test && isConstant(node.test, true)) {
context.report({ node: node.test, messageId: "unexpected" });
}
} catch (error) {
if (error instanceof UnsupportedNodeError) {
context.report({ node: node.test, message: error.message })
} else {
throw error
}
}
}
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.
Even if the runtime doesn't support a syntax, ESLint should check the syntax successfully if ESLint supports it. In other words, the result of linting should be the same on all supported runtimes.
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.
In other words, the result of linting should be the same on all supported runtimes.
Can we achieve runtime consistency by not supporting bigint on advanced runtime? Though bigint will not behave the same as numbers, just like numbers not behaving the same as booleans currently.
function isNodeTruthy(node) {
// TODO: bigint is not supported. wait till node v10.4.0+ is popular.
if (typeof node.value === 'bigint' || (node.value === null && node.raw !== 'null')) {
return false;
}
return !!node.value;
}
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.
As per ESTree spec, the Literal
nodes of BigInt have bigint
property with the text representation of the bigint value. And the Literal
nodes of RegExp have regex
property with { pattern: string; flags: string }
. The value
property of Literal
nodes of those may be null
, so we have to check the bigint
/regex
properties to distinguish null
literal and the others.
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.
Currently, eslint is using espree 6.2.1, which does not support bigint or regex with u
flag. 🤦
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 actual version is using espree 7.1.0
Line 59 in b7d79b1
"espree": "^7.1.0", |
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 was using an old version. Anyway, fallback handling is added. Your suggestions would be helpful.
https://github.com/eslint/eslint/pull/13245/files#diff-aee426385c16cd9eadb5fabd23dfa0bfR64-R84
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 code below is working on node 10.0.0.
if(a);
if(/foo/ui);
if(0n);
if(1n && a);
if(1n || a);
2:4 error Unexpected constant condition no-constant-condition
3:4 error Unexpected constant condition no-constant-condition
5:4 error Unexpected constant condition no-constant-condition
@Zzzen sorry for the delay! I'm marking this as an accepted bug fix, based on comments and +1s from team members in #13238. For example, it doesn't make sense to treat This is behavior in the actual version of this rule, and it looks inconsistent: /* eslint no-constant-condition: error */
if (false && a); // error
if (void 0 && a); // error
if (0 && a); // ok
if (null && a); // ok This PR fixes that, and several other bugs. Edit: also doesn't make sense to check values on the right side, but not on the left side as it is in the actual version: /* eslint no-constant-condition: error */
if (a || 1); // error
if (1 || a); // ok |
lib/rules/no-constant-condition.js
Outdated
// https://tc39.es/ecma262/#sec-literals-numeric-literals | ||
if (typeof node.bigint === "string") { | ||
return !(/^0+$/u.test(node.bigint) || /^0[box]0+$/ui.test(node.bigint)); | ||
} |
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 is well-done, but I'd prefer to remove this branch. All Node versions we support now will have the bigint value in node.value
, and we're already using that fact in some rules, like yoda
.
In a worst-case scenario (e.g., an old browser, though we don't officially support use of ESLint in browsers) bigint literals will be just ignored, which doesn't look that bad.
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 would simplify implementation a lot.
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 the very quick response!
Sorry if my post was confusing, I meant to remove only the bigint part.
This was ok:
// regex is always truthy
if (typeof node.regex === "object") {
return true;
}
Regex syntax may have more changes in the future, so we will always use node.regex
in rules.
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.
🤦♂️ fixed.
lib/rules/no-constant-condition.js
Outdated
return !(/^0+$/u.test(node.bigint) || /^0[box]0+$/ui.test(node.bigint)); | ||
} | ||
|
||
throw new Error(`unsupported literal: ${node.raw}`); |
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.
We aim to not intentionally crash on unknown syntax.
Can we return null
in this case, and explicitly check for true
/false
in isLogicalIdentity
?
Since it will be a 3-state, maybe we could also rename this function to getBooleanValue
.
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.
Done
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 code looks great, I have just a small JSDoc suggestion.
Co-authored-by: Milos Djermanovic <milos.djermanovic@gmail.com>
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.
Looks great, thanks!
Also thanks for fixing several different bugs and cleaning up the code.
Great work, thanks for contributing! |
Prerequisites checklist
What is the purpose of this pull request? (put an "X" next to an item)
[ ] Documentation update
[x] Bug fix (template) #13238
[ ] New rule (template)
[ ] Changes an existing rule (template)
[ ] Add autofixing to a rule
[ ] Add a CLI option
[ ] Add something to the core
[ ] Other, please explain:
What changes did you make? (Give an overview)
Previously, only boolean literals are considered in
isLogicalIdentity
. All literals will be coerced into boolean now.Is there anything you'd like reviewers to focus on?