JS Tip of the Day: Missing Semicolon ASI Gotchas

Missing Semicolon ASI Gotchas
Level: Intermediate

For the most part, semicolons used for marking the end of a line of code are optional in JavaScript. If not added by you explicitly, they’ll be added for you automatically. This process is known as Automatic Semicolon Insertion, or ASI for short. If you wish to rely on ASI for your code rather than adding semicolons yourself, you should be aware of the following gotchas where ambiguity can cause ASI to behave unexpectedly, specifically where it is not inserting a semicolon when you might expect it to.

When a line starts with [ bracket access or array literal?

Consider the following:

let x = 1
[1,2,3].indexOf(x)
// TypeError: Cannot read property 'indexOf' of undefined

You might expect [1,2,3] to be an array literal, starting a new statement below the declaration of the x variable above it. However, in this case ASI does not add a semicolon after the first statement, so when executed, this code is seen as:

let x = 1[1,2,3].indexOf(x)

This then causes the square brackets to be seen as bracket access notation for the 1 which, in turn, treats the values inside as a comma-separated list resolving the last value in that list or 3. What you get is 1[3].indexOf(x) or, since Number values do not have a 3 property, undefined.indexOf(x) which causes the error.

This is a case where, to prevent this from happening, you would need to explicitly provide a semicolon after the first statement because ASI was not providing one for you.

let x = 1;
[1,2,3].indexOf(x) // 0

Now, the declaration of x is correctly terminated at the 1 and [1,2,3] is seen as an array literal. This means indexOf() will correctly be called from an array value rather than undefined.

ASI here, and in additional examples outlined below, can’t tell from the line following the declaration of x whether or not it is supposed to be used with the previous line or not since it would be valid syntax (despite possibly throwing a runtime error) in either case. What it ends up deciding to do is not including an automatic semicolon - something which may actually be right in some cases. It just happens not to be right in this case.

Starting a line with an array literal is one of the more likely circumstances you might run into this, but there are a few others as well…

When a line starts with ( function call or grouping?

let x = 1
(() => x)()
// TypeError: 1 is not a function

Here, the first set of parenthesis containing the function are treating 1 like a function, calling it as 1(() => x). With an explicit semicolon, the parens are a group wrapping the function rather than being seen as a function call:

let x = 1;
(() => x)() // 1

When a line starts with / division or RegExp literal?

let x = 1
/abc/i
// ReferenceError: abc is not defined

This is being seen as the division of the values 1 / abc / i, but since abc (or whatever other character string you might have in a regular expression) is not a defined variable, we get an error. With a semicolon, the forward slashes are seen as the start of a regular expression literal:

let x = 1;
/abc/i // /abc/i

When a line starts with ` tagged template literal or template literal?

let x = 1
`text`
// TypeError: 1 is not a function

Here, 1 is being seen as a template tag (which should be functions) for the template literal below it. With a semicolon, its an untagged template string:

let x = 1;
`text` // "text"

When a line starts with + unary + or addition?

let x = 1
+'1'
// x = "11"

This is one statement assigning x to 1 + '1'. With a semicolon, there’s two statements, the second being a unary + which converts the string “1” to the number 1:

let x = 1;
+'1' // 1
// x = 1

When a line starts with - unary - or subtraction?

let x = 1
-'1'
// x = 0

This assigns x to 1 - '1'. The semicolon again allows separation, with the second statement being a unary - which converts the string “1” to the number -1:

let x = 1;
-'1' // -1
// x = 1

More info: