JavaScript の不思議な挙動を追ったらセミコロンの仕様について知れたよ

# TL;DR
- 「JS(ECMAScript)では、改行でセミコロンを代用できる」と勘違いしていた
- 実際は、以下のキーワードを解釈する際に文法上許されないトークンが出てきたら、**その間にセミコロンを挿入している**
    - let, const
    - import, export
    - Expression statement
    - debugger
    - continue, break, throw
    - return

- 具体的には`}`や`line terminator`ごとに分解してセミコロンを挿入する
- 上記の構文を使う時、改行で記述を分けているつもりでも、セミコロンがなければ**予期しない解釈**をされることがある
 
# `;`を忘れただけなのに 😢
皆さん、以下のコードはどんな出力になると思いますか？
``` constArray.js
const x = {
  y:1
}

["a","b"].forEach((v)=>console.log(v))
```

普通に以下のような出力を想像したのではないでしょうか？

```
a
b
```

残念、これは不正解です。
実際には以下のようなエラーになります。
```
Error: Cannot read properties of undefined (reading 'forEach')
```
いやいや、Array ちゃんと宣言してるし、なんで undefined 扱いになってるの？？

# 実験
理解できない挙動になったので、色々と実験してコードがどう解釈されてしまっているのか確かめます。

## 1. forEach の削除
まずは、`forEach` 以降を無くして、どう解釈されているか見てみましょう。

``` removeForEach.js
const x = {
  y:1
}

["a","b"]

console.log(x) // 出力結果: undefined
```
ん？？
これってもしかして、Array の部分までくっついて`x`として解釈されてない？
つまり、以下のような形で、`x`のプロパティの参照として解釈されているのでは？

``` hypothesis.js
const x = {
  y:1
}["a","b"];
```

## 2. `x`のプロパティの変更
じゃあ、以下のようにして`x`に`a`と`b`のプロパティを入れてやれば参照できる？
``` try.js
const x = {
  a:1,
  b:2
}["a","b"];

console.log(x) // 出力結果: 2
```

おお〜ちょっと気になる挙動だけどそれは置いといて、ちゃんと`x`のプロパティの参照として解釈されてるっぽい。

## 3. 行末へのセミコロンの追加
ここまでわかったことをまとめると、お題のコードは以下のような流れで実行されたと考えられる。
1. `x`と`["a","b"]`がくっついて解釈
2. 1. により`x`の存在しないプロパティへの参照を実行
3. `undefined`を出力

これに対する解決策は単純で、1.が起きないように、行末にセミコロンを追加すればいい。
``` semicolon.js
const x = {
  y:1
};

["a","b"].forEach((v)=>console.log(v));

console.log(x);

/*

実行結果
    a
    b
    { y: 1 }

*/
```

おお、やっぱりセミコロンを入れたらちゃんと動いたね！ forEach も動いたし、`x`の中身も問題ない。

めでたしめでたし。

# 何が起こったのか
いやいや、ちょっとまて。
JS ではセミコロンなくても動くんじゃなかったっけ？？
連続した空行とかで勝手にステートメントの区切りを判断してるんじゃないの？

どうも実験の結果をみると、それとは違う処理を行っていることが今回エラーの原因と考えられる。

そこで、「js semicolon」などのワードでで色々調べてみると、 [これ](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#automatic_semicolon_insertion
)が見つかった。以下はその抜粋。

> Some JavaScript statements' syntax definitions require semicolons (;) at the end. They include:
> 
> ・　var, let, const
> ・　Expression statements
> ・　do...while
> ・　continue, break, return, throw
> ・　debugger
> ・　Class field declarations (public or private)
> ・　import, export
>
> 訳：JavaScript文の構文定義には、最後にセミコロン（;）が必要なものがあります。それらは以下の通りです。

> However, to make the language more approachable and convenient, JavaScript is able to automatically insert semicolons when consuming the token stream
>
> 訳：しかし、言語をより親しみやすく便利にするために、JavaScriptはトークン・ストリームを消費する際に自動的にセミコロンを挿入することができます

おお〜！jsはステートメントを連続した空行で区切ってるわけではなく、必要なトークンを定義するときに自動的にセミコロンを挿入しているのか。

さらに読みすすめると、、

> When a token not allowed by the grammar is encountered, and it's separated from the previous token by at least one line terminator (including a block comment that includes at least one line terminator), or the token is "}", then a semicolon is inserted before the token.
>
> 訳：文法上許されないトークンがあり、それが前のトークンと少なくとも1つの行末（少なくとも1つの行末を含むブロックコメントを含む）で区切られているか、トークンが"}"である場合、その前にセミコロンが挿入されます。

これが今回の原因っぽい！

つまり、いつもならセミコロン無しで`x`のようなオブジェクトの`const`宣言を行う時でも、`}`の後の記述は文法許されないトークンとなるため、セミコロンが改行の前に挿入される。

``` auto-insertion-semicolon.js
// 「const x = { y:1 }console.log(x);　」は文法上許されない
const x = {
  y:1
}// よって、「文法上許されないトークンと前のトークン（x）の間」のこの位置にセミコロンが挿入される

console.log(x);
```

しかし、お題のコードは、`}`の後の記述が、たまたま文法上許されるものになってしまった。
そのため、`x`の存在しないプロパティへの参照という動作になった。

``` error.js
const x = {
  y:1
}// 「const x = {y:1}["a","b"]」 は文法上許される

["a","b"].forEach((v)=>console.log(v))
// なので👆ここまで１つのトークンとして解釈されてしまう
// それにより、実行結果は undefined
```

# まとめ
この現象は、以下ののステートメントで起こりうる。
- let, const
- import, export
- Expression statement
- debugger
- continue, break, throw
- return

特に`let`や`const`を使う場合はセミコロンを挿入することを癖付けよう。

# 参考文献
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Lexical_grammar#automatic_semicolon_insertion