I tried looking for some explanation but failed. What is an actual reasoning behind changing the default of this rule from except-parens to always?
The most typical example is looping over multiple matches of a single regexp.
var myRe = /ab*/g;
var str = 'abbcdefabh';
var myArray;
while ((myArray = myRe.exec(str)) !== null) {
var msg = 'Found ' + myArray[0] + '. ';
msg += 'Next match starts at ' + myRe.lastIndex;
console.log(msg);
}
Is there a recommendation how to write this differently without breaking that rule setting? I tried do...while but there that's either weird while(true) or a duplicate condition which feels rather weird.
do {
var myArray = myRe.exec(str)
if (myArray === null) break;
var msg = 'Found ' + myArray[0] + '. ';
msg += 'Next match starts at ' + myRe.lastIndex;
console.log(msg);
} while(true) // or while(myArray !== null)
Until https://github.com/tc39/String.prototype.matchAll becomes part of the language, a while loop, or a .replace abuse, is the only way to achieve what you want. Thus, in the meantime, I'd recommend something like the following:
function matchAll(str, regex) {
const matches = [];
str.replace(regex, function () {
var match = Array.prototype.slice.call(arguments, 0, -2);
match.input = arguments[arguments.length - 1];
match.index = arguments[arguments.length - 2];
matches.push(match);
});
return matches;
}
const myRe = /ab*/g;
const str = 'abbcdefabh';
const matches = matchAll(str, myRe);
matches.forEach((match) => {
const { lastIndex } = match;
const msg = `Found ${match}. Next match starts at ${lastIndex}`;
console.log(msg);
});
@ljharb Thanks, that looks similarly terrible like that solution with do...while :D
Honestly, I am more interested in reasoning for this rule setting because there is obviously no clean solution without breaking it. I like airbnb rules and I am using them mostly without changes, but this rule setup just doesn't make any sense to me. Why is it bad to allow condition assignment when it's wrapped in parens?
In this specific situation - where there's a gap in the language, and an active proposal to fix it (one I'm personally championing) - I'd recommend writing your own matchAll abstraction that overrides whatever eslint rules you need to, so that your real code doesn't have to.
In general, conflating statements (assignment) with expressions (a conditional evaluation) is a really bad idea for code clarity. Also, the return value of a = b is not intuitive, and it's really difficult to determine if you meant to do a == b or a === b - a = b in expression position looks like a bug.
Ok thank you for the explanation. I'll probably just go with this for now which is much more readable and later I can replace it with real matchAll.
function matchAll(regex, text) {
const matches = []
let match = null
// eslint-disable-next-line no-cond-assign
while ((match = regex.exec(text)) !== null) {
matches.push(match)
}
return matches
}
Sounds good, since it's hidden behind a forward-looking abstraction that can later be replaced by return [...text.matchAll(regex)], the merits of individual eslint-overridden implementations of that abstraction are a much, much less important debate :-)
why not just ban the while statemement altogether? When would you use a while loop without an assignment in it? all other uses for while are covered by other constructs.
I don't often use while statements in JavaScript, they are very nice from a loop invariant perspective.
while(!condition) {
foo;
}
// condition is now true
(or for the full example)
while(!condition) {
// Invariant is true and condition is false
foo;
// Invariant is still true
}
// condition and Invariant are now both true
Yes you don't need while loops and can do everything they do with a for loop (or other constructs) but I still think they are the cleanest solution for certain types of code. That said, I hardly ever use either while or for loops either as almost everything I do is a map, forEach, filter or reduce these days.
@lastobelus it is banned altogether; the airbnb guide and config bans all loops of any kind. In this case, it's an eslint override comment on an abstraction which can later be replaced by a native language feature.
@aboyton in a non-concurrent environment it's necessarily true that condition is going to have a variable in it that gets assigned to in the body of the loop, and in my opinion it produces more maintainable code to have that assignment in the same place as the condition. My argument is not necessarily in favour of banning while altogether; my argument is that IF you're going to ban assignment in the while condition, while no longer has much utility.
I personally don't see much value bothering with the code needed to replace while(match = regexp.exec), although I will certainly use matchAll when/if it becomes canon.
Most helpful comment
In this specific situation - where there's a gap in the language, and an active proposal to fix it (one I'm personally championing) - I'd recommend writing your own
matchAllabstraction that overrides whatever eslint rules you need to, so that your real code doesn't have to.In general, conflating statements (assignment) with expressions (a conditional evaluation) is a really bad idea for code clarity. Also, the return value of
a = bis not intuitive, and it's really difficult to determine if you meant to doa == bora === b-a = bin expression position looks like a bug.