Automatic Semicolon Insertion: The Lines That Join and the Return That Ends
JavaScript inserts a semicolon at a line break only when the next token cannot continue the statement. That makes semicolon-free code safe almost everywhere and dangerous in a few specific places. The ASI checker finds them.
Lines that continue the previous one
| A line that starts with | Joins the previous line as | Example |
|---|---|---|
( | a call | const a = b then (f)() means b(f)() |
[ | an index | const a = b then [1, 2].map(f) means b[1, 2].map(f) |
| a template literal | a tagged template | x then ` y means xy ` |
+ or - | a binary operator | a = b then -c means a = b - c |
/ | a division | a = b then /re/.test(s) means b / re / .test(s) |
If the previous line ends in a value (a name, a string, ) or ]) and has no semicolon, the two lines are one statement. Defensive style puts a ; before such a line or ends every statement with one.
Restricted productions
return, break, continue and yield end at a line break. This returns undefined, and the object on the next line is a separate, unreachable block:
function f() {
return
{ ok: true }
}
Put the value on the same line as return. The checker flags a return/break/continue/yield followed by a line break and a token.
After a closing brace
A line starting with (, [ or a template right after } is only a problem when that brace ended a function expression or object literal rather than a block. A tokenizer cannot tell which, so the checker reports it as "maybe".
What the checker does not do
It is a token-level scan, not a parser. It can miss a hazard, and a clean result means none of these patterns were found. Pair it with a linter rule (no-unexpected-multiline) in CI.