Generally, the rule seems to be "insert a semicolon wherever the syntax messes up but can be fixed by adding semicolons". Then there are some special cases (++/--/continue/break/return/throw/arrow functions/yield) with more specific rules about where semicolons are placed and when; those are probably why the example produces "unexpected" results.
I think the added complexity of these special rules and cases are an unnecessary burden, and honestly the language should never have allowed leaving out semicolons when it was clearly designed to have semicolons.
Seriously, semicolons are something that was baked into the design assumptions of JavaScript. Python's an example of a language where the syntax was clearly designed from the inception to avoid them. Most such languages make explicit tradeoffs and TBH I'd much rather have semicolons than whitespace sensitivity.
Periods are not needed if each sentence span exactly one line, which is the case in programming languages. the separators &&, ||, () do the intra statement disambiguation while for interstatement {} suffice. ; bring no value in practice. Using multiples semi colons on a single line is pathological code that should never exist in the first place.
When I code in Kotlin (which is not whitespace sensitive like python) I really realize that reading semicolon less code is smoother, it takes less cognitive resources (parsing is faster) and is more pleasant because the reading is not interrupted by vacuous symbols. There are no ambiguities in practice. The thing is, habits take time to die but ask anyone that has experience in an officially semi-colon less programming language, most will tell you it is a net improvement. BTW the vast majority of newer programming languages are semi-colon-less (Kotlin, Go, Swift, Julia, Scala, etc)
A series of individual invocations of arcane commands; sometimes with pipes
Though it is not uncommon for one-liners to exist (sometimes with sub-shells). In that case the semicolons are not optional. Even in cases where another type of escape is used to visually break up the one liner into a list like series of commands which happen to be run concurrently; most frequently for really long pipe-sets.
return (
[item]
)
or for this case: return [
item
]
You could do other things to prevent it from sticking out of the side of the monitor but if it is to say return true if this and that and that and that and that it would (to me) look better on separate lines. let c = a + b
(() => { ... })()
This is evaluated as: let c = a + (b(() => {...}))()
which usually results in "TypeError: b is not a function." Obviously this can be solved with a semicolon after the "let c..." line, but another common approach is to just prefix all IIFEs with semicolons, since empty statements are no-ops in JS: let c = a + b
;(() => {...})()
I personally prefer to forego semi-colons, but I have a hard time getting worked up over it either way.