◆ ASI の仕様です
◆ ASI を理解して快適なノーセミコロンライフを送りましょー 
◆ 文ごとに改行していれば 特定の文字から始めるときに ; から始めるだけ 

Automatic Semicolon Insertion

JavaScript はコード書く側からするとセミコロンいらない言語になりますが 内部的にはセミコロンを使って文を区切っています
JavaScript のパーサが文の終わりと判断できたらセミコロンを自動で挿入してくれるので 書く側はセミコロンを使う必要がありません

どんなときにセミコロンが挿入されるのかは ECMAScript のスペックに Automatic Semicolon Insertion (ASI) という名前で定義されています

最近リリースされた ECMAScript 2016 の仕様にあわせて解説します
ほとんど Google 先生に頼りきりな翻訳で読んでますので 変なところがないともいえませんが コード動かして確認してるのできっとだいじょぶなはず


セミコロンの自動挿入は 3 つのルールに従って行われます

1 つめ

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:

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

テキストの流れに沿ってパースしていって 文法で許可されてないトークンが出てきた時に 以下の条件にあてはまっていればそのトークンの直前にセミコロンが挿入されます

  • 直前のトークンとの間に改行文字がある
    JavaScript の改行文字はこの 4 つ
    • <LF>
    • <CR>
    • <LS>
    • <PS>
  • トークンが 「}」
  • 直前のトークンが 「)」 でセミコロンを挿入した場合に do-while の終了のセミコロンになる


単純に改行を挟んで 構文的に書けないはずの文字があれば文を区切ってくれるということです

また
var a = function(){return 0}
の 0 のあと
ここは改行がないですが return の文の後なのに セミコロンなしで動きます

「}」 でも
var a = {a: 1}
の場合は 1 の前にセミコロンが入りません
セミコロンを挿入してくれるのは文法的に許されないトークンが来た時だけなので オブジェクトリテラルの場合は文法的におかしくないのでそのままです

do-while の終わりでもセミコロンを自動挿入してくれます
do {} while(0) console.log(1)

こんなのでもおっけいです

改行ごとに文を分けるという考えじゃなくて 文法としておかしいときに文を分けて対処できるなら分けるという考えなので こんなのでもいいみたい

普通に
do {
}
while(0)
console.log(1)

と書いたほうが見やすくて綺麗と思うのでこれはほとんど覚えておかなくても大丈夫だと思います

この do-while は ES5 のときの仕様には書かれていなかったので ES6 からのようです
ブラウザ的にいつから使えたかは知らないですが IE エミュレータだと IE5 でも動いてるので書かれてなかっただけで初期の頃からこうなってのかもしれません

2 つめ

When, as the Script or Module is parsed from left to right, the end of the input stream of tokens is encountered and the parser is unable to parse the input token stream as a single complete ECMAScript Script or Module, then a semicolon is automatically inserted at the end of the input stream.

入力ストリームの終わりで 入力されたストリームが完全な ECMAScript のコードとしてパースできないなら ストリームの最後にセミコロンを挿入します

.js ファイルを読み込んで一番最後まで行った時にエラーがあれば最後にもセミコロン入れてみるってことですね
例えば .js ファイルが
var a = 1
var b = 2

だけだったとします
1 行目の後は 1 つ目のルールで改行を挟んでるのでセミコロンが挿入されますが 2 行目の最後には改行がないのでエラーです
そんなときに最後にセミコロンを入れて 2 行目も文として正しい形にしてくれます

単純に 1 のルールだと改行挟んでるかなので改行なしのファイルの最後にも対応させたというだけですね
入力ストリームなので ファイルに限らずコンソールの REPL で一行ずつ実行しているものや eval の文字列などでも行われているのだと思います

3 つめ

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.

あるトークンをパースしたときに 文法的にはあってるけど文の種類が特定のものかつトークンがその文中の 「no LineTerminator here」 の直後にあって その前のトークンとの間に改行文字があるときはトークンの直前にセミコロンを挿入します

よくわからないように感じますが 一部の文には no LineTerminator here というのが設定されてます
return [no LineTerminator here] Expression

みたいな感じです

この no LineTerminator here は書いてある通りでここに改行文字を書くことができないということです
改行文字は 1 つ目のルールのところにある 4 種類のことです

改行文字があった場合はそこで文を切ってしまうので 上の return 文だと
return
    100

と書くと
return;
    100;

と扱われるので 100 は return の文とは別の文になって思い通りの値が返ってこないことになります


この特定の文というのは以下のものです
UpdateExpression:
    LeftHandSideExpression [no LineTerminator here] ++
    LeftHandSideExpression [no LineTerminator here] --

ContinueStatement:
    continue ;
    continue [no LineTerminator here]LabelIdentifier ;

BreakStatement:
    break ;
    break [no LineTerminator here]LabelIdentifier ;

ReturnStatement:
    return ;
    return [no LineTerminator here] Expression ;

ThrowStatement:
    throw [no LineTerminator here] Expression ;

ArrowFunction:
    ArrowParameters [no LineTerminator here] => ConciseBody

YieldExpression[In]:
    yield [no LineTerminator here] * AssignmentExpression
    yield [no LineTerminator here] AssignmentExpression

例外

However, there is an additional overriding condition on the preceding rules: a semicolon is never inserted automatically if the semicolon would then be parsed as an empty statement or if that semicolon would become one of the two semicolons in the header of a for statement (see 13.7.4).

あとは 例外で セミコロン入れても空文になるときや for 文のヘッダの 「() 」の中のセミコロンになる場合は挿入されません
if(true)

else
  console.log(1)
for(
    var i=0
    i<10
    i++
){}

はできないです


セミコロンの必要性

長かったですねー

英語の分量はそれほどないはずなのに一日くらいはかかってましたよ


で セミコロンは必要なの?ということですが 上の説明を見るとよくわからなくて複雑だから書くようにしようとかいう人が多いです

ですが せっかくここまで色々とセミコロンを書かなくて良いようにしてくれてるのにそれを捨てる方がありえないと思いませんか?

セミコロンを書かない場合の行の結合

仕様という形なので複雑に見えてなが~いですが 実際にやってることはシンプルです

改行を入れると ECMAScript の文法上来れない文字があれば セミコロンをいれてくれます
ファイルの最後では改行なくてもいれてくれます

for 文のセミコロンは明らかに特殊なのでセミコロンいれてくれないのは自然ですし do-while や } は丁寧にインデントしてるコードなら気にする必要もないです


なので 文法上続くことができるものが行を超えて繋がるところだけ気をつければいいんです
行を超えて繋がるものがどんなものかというと
var a = 1
-2

がわかりやすいと思います
これは繋げたいものなはずです

これで文を分けてくれという方がおかしいです


問題が起きるのは 分けたいのに繋がってしまうことです
例えば
var a = []
(function(a){
    console.log(a)
})(1)

[] は式なので関数の可能性もあるので直後に () があれば関数呼び出し扱いができます
なので [] を関数とみなして 無名関数を引数として実行してエラーになります

[] is not a function

こういうのが起きるのが問題ですが こういうことはほとんどないです
対処も行の最初が前の行と繋がることができる文字の場合に セミコロンから始めればいいだけです
var a = []
;(function(a){
    console.log(a)
})(1)

こういうのです
無名関数の最初にセミコロンがあるのは見たことある人多いのではないでしょうか

これをこの場合は前とつながるから~ と毎回考えているのは大変ですしそんな苦労がいるならセミコロン書けばいいと思います
ですが 苦労もなく一部の文字から始めるときだけセミコロンから始めるようにだけすれば深く考えずに前の行と繋げずにコードが書けます

  • (
  • [
  • `
  • /

3 つ目は ES6 のテンプレートストリングです
関数の直後に書いてタグとして扱えるので 前の行の式と繋げることができます

4 つ目は 正規表現リテラルの場合です
普通の割り算のときやコメントの時には考えなくていいです
ただ 正規表現リテラルから行が始まるなんて年に 1 回見ればいいくらいに珍しいものです
ただでさえ match や replace の引数の中でしか使わないのに 代入もせずリテラルから始めることなんて 代入しないで結果を表示してくれる REPL くらいかと思います
/a(.)c/.exec("axc")
// ["axc", "x"]

どういう動きになるのかを書いておくと 式の途中で / があると正規表現リテラルとみなされず割り算の / と扱われます
var a = b
/regexp/ig.exec("str")

こういう変数を準備しておけば動きます
var b = 64
var regexp = 4
var ig = {exec: function(){return 8}}

var a = b
/regexp/ig.exec("str")

console.log(a)
// 2
// (b) / (regexp) / (ig.exec("str")) → 64 ÷ 4 ÷ 8


とりあえずこれだけ気をつければ何千行 何万行と JavaScript 書いてますが困ったことはないです
4 つ目はまず出会ったことが無いです

最初に前の行と繋げられる文字を書かないようにすれば

1 つめと 2 つめも最初に ( や [ が来るって気持ち悪いのでそういうことがないような書き方してしまう手もあります
!function(){
}()

例えば無名関数の即時実行は ! にします
! は前の行とつながらないので安心です

! の欠点は返り値が Boolean キャストされて反転してしまうことですが ( から始めている以上返り値は要らないはずです
返り値が必要な場合はどこかに代入しているはずなので返り値が必要なものは ( から 副作用を起こすだけのときは ! からと使い分けるのオススメです

セミコロン問題だけでなく 関数をぱっと見た時に DOM を書き換えるなどの副作用を起こすだけのものか 返り値を受け取るものかパット見てわかるのが便利です


他には
Array.of(1,2,3).forEach(e => console.log(e))

代入せず使う時はリテラルじゃなく Array.of にします
これまた 代入せず副作用を起こすわけなので違いがあるほうが見やすさがあります
コピペや使い方を変えるときに面倒ともいえますけど

return などの ASI

3 つ目のルールですが ここの仕様がおかしいとか余計な機能と言う人は多いです
私も最近までは 分かりづらい気がすると思ってました

ですが セミコロンを書かない言語として考えるとすごく自然です

例えば
function fn(arr){
var a
if(arr.length === 0) return
a = arr.pop()
arr.unshift(a)
}

この関数は配列の最後の要素を最初に移動するものです
空配列なら即 return するようになってます

ここで return の後の改行でセミコロンが挿入されない場合は
function fn(arr){
var a;
if(arr.length === 0) return a = arr.pop();
arr.unshift(a);
}

こういう動きになります
return する値に a = arr.pop() の式が使われてしまいます

こうならないために return の直後の改行はそこで文の終わりとみなしてくれるわけです

if 文には毎回複文の {} を入れるとか return で何も返さないなら undefined を明示するとかするようにすればいいですが そうじゃなくセミコロンを入れてくれるあたりセミコロンを書かない考えが優先されてそうです


また
var a = 1
var b = 2
a
++ b

これは b のほうに ++ がくっつきます
前から読むなら a にくっつきそうですが 改行があると b になります


こういう部分の理由はセミコロン書かずに改行で文の終わりを表す言語として考えると自然なので セミコロン書かない方が正しいのではと考える理由になりますね

よくわからないことなんて起きない

JavaScript でセミコロンを書く書かないの話題では

省略したらよくわからないことが起きるらしい」 とかいう人がいますよね


よくわからないことなんて起きません
書いてる通りに動いているだけです
文がどこで区切られるかは最初に書いた ASI の仕様どおりです

JavaScript の文法を把握してれば困ることはないはずです


ブラウザで簡単にちょっとした計算できるから 程度に使うライトな人や 専門じゃないけど仕方なく使っていて動けばいいやくらいなデザイナーさんなどは 「ASI? なにそれ」 でもいいですからセミコロン書くのが苦痛に感じないなら書けばいいです

ですが JavaScript のプログラマを名乗っている人で「よくわからないことがことがおきる」なんていうと この人詳しくないんだな とかこの人のコードはあまり見たくないなと感じてしまいます
理解した上で普段 C# とか PHP とか Perl とか使ってるから癖で書いたほうが楽などの理由ならいいのですが 仕様がわからないからとりあえずな人がセミコロンは書くべきとかいう情報をドヤ顔で流すのはホント迷惑です


(セミコロン書かないほうが Python などのように書き方がある程度決まってきて汚いコードが書きにくいというのもいいところだと思うので ちゃんとプログラム書く気なら初心者ほどセミコロンなしがいいんじゃないかとも少し思ってます)

まとめ

行の最初が特定の文字から始める時だけ ; から始めるだけで全部の文末にセミコロンを書く必要がないんです

日本語だって 毎回文の最後に「。」を書くかと言われたら作文だったり論文だったり報告書だったりと正式な文章とかでもなければ書かないひとが多いですよね
ブログや掲示板やチャットで「。」なんてめったに見ないと思います
改行で意味が伝われば改行で十分です
伝わりづらいところがあれば稀に「。」をつけるかもしれません
JavaScript もそれと一緒です