loading...
Cover image for What you need to know about Javascript's Automatic Semi-colon Insertion

What you need to know about Javascript's Automatic Semi-colon Insertion

promhize profile image Promise Tochi Updated on ・3 min read

Omitting semi-colons is a convenience that Javascript affords you. Javascript allows you to omit semi-colons in places where they’d normally appear, that is, at the end of statements or by convention before a new line character.

const foo = "food"; const randomNumber = Math.random()

Which can be rewritten as

const foo = "food"
const randomNumber = Math.random()

This is made possible thanks to the Automatic Semi-colon Insertion rules included in the official ECMAScript specification.

It’s a convenience though that if not properly understood can introduce a lot of Javascript gotcha scenarios. So, in this article I’ll talk about how the automatic semi-colon insertion works.

It might not be apparent that the two code snippets below produce totally different results,

/**/
const val = "foo";
['semi']
console.log(val) //foo
const foo = {
  semi: 'not semi'
}
const val = foo
['semi']
console.log(val) //not semi

If you are wondering why that is so, here’s what the ECMAScript 6 specification says about how automatic semi-colon insertion should work:

When, as a Script or Module is parsed from left to right, a token (called the offending token) is encountered that is not allowed by any production of the grammar, then a semicolon is automatically inserted before the offending token if one or more of the following conditions is true:

  1. The offending token is separated from the previous token by at least one LineTerminator
  2. The offending token is }
  3. The previous token is ) and the inserted semicolon would then be parsed as the terminating semicolon of a do-while statement

I have tried to explain the rules above with the code sample below

/* Rule 1.1 */
const foo = 'food'
const bar = 'bar' /* Yo, I found token const on a new line, parsing it with the previous line as a single statement doesn't make sense, insert semi-colon before line 3 */

/* Rule 1.2 */
const baz = () => {
    const boo = true
  return boo } /* Found token }, insert semi-colon before it, parse previous statement */

/* Rule 1.3 */
do {
    const GT = foo + 'bar'
} while (baz()) /* Insert semi-colon after closing parentheses, gold could be on the next line for all I care */

Another way to sum up the first rule is,

“Hey, JS engine, if there’s no error parsing the code, continue and ignore the new line character. If there’s a parse error, do all these checks and insert semi-colon where necessary.”

The important thing to note here is, browsers will not insert semi-colons at the end of a line, if parsing a new line together with the previous line as one single statement, still results in valid Javascript. Back to the example at the beginning of this article:

const foo = {
  semi: 'not semi'
}
const bar = foo
['semi']

Even though [‘semi’] is on a new line, parsing it together with the previous line still results in valid Javascript.
So it is parsed as const bar = foo[‘semi’], which results in ‘not semi’
While omitting semi-colons, developers must take special care when starting new lines with these characters:

  1. [
  2. (
  3. +
  4. /
  5. -
  6. {
const one = '1'
const two = 1
+one // this is a common technique for converting type 'string' to 'number'
console.log(two) // 11

It is common to see code similar to the example below in JS projects, and it might not become immediately apparent why it parses as correct Javascript grammar, but it is thanks to the Automatic Semi-colon Insertion rules we just discussed.

object
.childObject
.method()

The specification also state that,

When, as the Script or Module is parsed from left to right, a token is encountered that is allowed by some production of the grammar, but the production is a restricted production and the token would be the first token for a terminal or nonterminal immediately following the annotation “[no LineTerminator here]” within the restricted production (and therefore such a token is called a restricted token), and the restricted token is separated from the previous token by at least one LineTerminator, then a semicolon is automatically inserted before the restricted token.

This simply says no line breaks after certain tokens (keywords) like return, break, postfix expressions (- -, ++), throw, etc.

return //semi-colon is automatically inserted here
4 // no

break //semi-colon is automatically inserted here
a //no

continue //semi-colon is automatically inserted here
a = 4

const arr = arr () //semi-colon is automatically inserted here
=> {} // no

...

I hope you can now write Javascript without semi-colons with more confidence :)

Posted on by:

Discussion

pic
Editor guide
 

Or you can save yourself the trouble and hidden bugs in the future and use semicolons everywhere :) Remember, you may know all the rules by heart but the next junior programmer may not.

eslint has a handy "semi": "error" rule you can enable to nag you about missing semicolons.

 

I love the omission of semi-colons. Code just looks much cleaner without unnecessary syntax. These situations that are exception rarely, if ever, come up on normal projects.

I built my language's grammar, Leaf, to not need semi-colons either -- and without any exceptions to the rules.

 

Good post!

Just a little typo in the first example:

const foo = {
  semi: 'not semi'
}
const val1 = foo
['semi']
console.log(val) //not semi

This would actually throw a ReferenceError, since val isn't defined (you define val1 before, not val)

I never use semi colons, except when I have to...

The only time I needed to do so was when I wrapped some code in a self calling function, like so:

(function () {
   console.log('*My* code!')
)()

If the previous line doesn't finish with a semi colon and that one isn't inserted automatically, for example, let's say the previous line was:

(function () { console.log('An other script') })()

What is going to happen is that my code is going to try to call the return value of the function just above (which is undefined), since there isn't any semi colon.

The whole code formatted so that it's easier to understand:

(function () {
    console.log('An other script')
})()(function () {
   console.log('*My* code!')
})()

This gives you:

Uncaught TypeError: (intermediate value)(...) is not a function
    at <anonymous>:3:5

The really vicious thing is that there is this problem when loading scripts in different <script> tag:

This is my buggy html page

<script src="myscript1.js"></script>
<script src="myscript2.js"></script>
// myscript1.js

(function () {
   console.log('This is my first script (it works)')
})()

// myscript2.js

(function () {
    console.log('But this doesn\'t...')
})()

The solution? Put a semi colon before every self calling function:

;(function () {
    console.log("this works in every case!")
})()

Did you know you could put more than one semi colon?

console.log("this");;;
console.log("works!");;;;;

Just a funny useless thing... :smile:

 

Yeah, thanks for pointing out the typo.

The multiple semi-colon is interpreted as multiple statements. Also the multiple IIFEs thanks for pointing that out too.

 

The effort of typing is always a very small fraction of the real cost of any code (in anything beyond throw-away side-projects), so (the effort of) having write or not write semi-colons tends to be very very low on the priority scale.

The real question is here is: is code with or without semi-colons in some way noticably more performant (in a way that matter) or more maintainable over time and over developers. There, I think the addition of semi-colons in JavaScript wins.

 

There are some caveats, but if you do not add new line in places that other languages would throw a compiler error it's ok.

I was a strong advocate of using semi colons in JavaScript, until I started learning Go (and omit them). Then I count how many semi colons I wrote in my previous small JS project (many thousands) and realized how much time I wasted on typing 😁.

 

In my JavaScript code, I always omit semi-colons unless they are required (which isn't often).

ASI for the win!

 

Using Prettier is the easy way out. Format on save and you see how bad you failed to add the right semicolons :D

 

Prettier is a great tool