"関数名を書き換える"嫌がらせプログラムを作ってみた - Babel Toolingの活用法

## 今から嫌がらせをします

こんにちは。どんぶラッコ(Twitter: [@don_bu_rakko](https://twitter.com/don_bu_rakko))です。
突然ですが、**今から関数名を書き換える嫌がらせをします**。

## 嫌がらせの手順

### 1. 関数が記述されているJSファイルを用意します

嫌がらせの対象となるJSファイルを作成します。
ここでは、関数 `fuu()`, `rin()`, `ka()`, `zan()` を作りました。風林火山。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/e46a3a69-5a6d-923f-a4d7-5e510e993109.png)

### 2. `rakko.js`を実行する

嫌がらせ開始です。
`node rakko.js [対象のjsファイルリソース]`を実行します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/6093c96d-9078-af56-9a8f-266814e27ee4.png)

### 3.ファイルが生成されるので開きます

これで嫌がらせ完了です。
`dist/` ディレクトリに生成されたJSファイルを開いてみてください。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/d0751646-d474-db49-f6a6-3ecf8f9feaa1.png)

### 4.その結果

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/aa85e370-4529-5dd6-6001-63b68c4ca98b.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/62484f24-16f2-a656-ff73-a377181b3a47.png)

**関数が全部`donBuRakko()`に変わってるーーーー！！！**


## 関数名を書き換える嫌がらせプログラム

ということで、関数名を全て `donBuRakko()` に変える嫌がらせのプログラムを作ってみました。
これの嫌なところは**宣言時の関数名のみ**を変換するということ。

つまり、武田信玄に塩を送る関数があったとしても

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/cf5587db-4ab3-f369-ccb4-5e14b61d11bc.png)

**でん！**
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/170c68b4-8d7a-bc9d-d769-0bd27bea2314.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/c7ed3960-f4bc-43e1-7c1a-3145b0ecd96c.png)

**isSendSalt is not defined**

こうして敵に塩が送れなくなります。**ただただ悪質な嫌がらせプログラム**ですね。間違っても先輩のPCで実行しないでください。絶対にです。

ところで、このプログラムは**Babel**のライブラリを使って作成されています。
ということでここからやっと今回のAdvent Calendarのテーマ、**お勧めしたいテクニック**の紹介に移ります。

## Babel Toolingを使い倒す！

### Babelとは

みなさん、Babel自体はご存知の方も多いと思います。新しいJavaScriptの構文を現在のWebブラウザでも使えるようにしてくれる、あれです。

例えばアロー関数で書かれたJavaScriptをBabelに通すと

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/b02a97e9-300c-84fa-52aa-5d83e86995d0.png)

こんな風にしてくれます。
今日の我々はBabelのおかげで、文法のブラウザ対応とかを気にすることなくJavaScriptを記述出来るわけです。超ありがたい。

### Babel Tooling

実は、このBabel、[公式ページ](https://babeljs.io/docs/en/)のドキュメントを読んでいると、**Tooling**という項目でいくつかのツールが提供されているのがわかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/82a073f8-1e2c-b18b-afe0-a2a09fb40641.png)

今回はその中でも `parser`, `genertor`, `traverse` を取り上げてみます。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/e0c55e69-2407-8bd9-dcbc-bb8a25a3f0be.png)

### Babel トランスパイルの仕組み

Babelがプログラムを組み替えることを**トランスパイル**なんて言ったりしますが、その仕組みはこうです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/a3b82a7e-86f2-5c85-efb6-4c2512840908.png)

- 形態素解析(Parse)
    - 記述されているプログラムがどんな要素で構成されているのか、構文木(AST)の形式で解析します。
- 置き換え(Traverse)
    - 置き換えのルールを作ることが出来ます。先ほどの`donBuRakko()`置き換え機能はtraverseの機能を使っています。
- コード生成(Generate)
    - 一度形態素解析でバラバラにしたコードを再度生成します。

そして、先ほどの`parse`, `traverse`, `generate` のツールを使うことでそれぞれを実装することが出来ます。ToolingはBabelの機能を個別に切り出して提供してくれている機能、というわけです。

### 実装

では、先ほどの関数名変更嫌がらせプログラムを例に、実装の方法をみていきましょう。
ここからは細かい話になるので、最初は流し読みでいいと思います。興味を持ったら読んでください。

下記のリポジトリにも格納してあります。
https://github.com/cha1ra/dbk_babel_ex


まずは、`npm init` して必要なライブラリをインストールします。

```
$ npm init
$ npm install --save @babel/parser  @babel/traverse @babel/generator
```

次に`rakko.js`を作ります。今回は`src/`というディレクトリを作成し、その中に格納してあります。

```javascript:/src/rakko.js

// ライブラリの読み込み
const parse = require('@babel/parser').parse
const traverse = require('@babel/traverse').default
const generate = require('@babel/generator').default
const fs = require('fs')
const path = require('path')

console.log('どんぶラッコに変換！')

// 引数・ファイルの読み込み
const arg = process.argv[2]
const ext = path.extname(arg)
const filePath = path.join(__dirname, arg)
const code = fs.readFileSync(filePath, { encoding: 'utf-8' })

// parse
const ast = parse(code)

// traverse
traverse(ast, {
  FunctionDeclaration: (path) => {
    path.node.id.name = 'donBuRakko'
  }
})

// generator
const result = generate(ast).code

// ファイルを dist/ ディレクトリに書き出す
fs.writeFileSync(
  path.join(
    '../dist',
    `${path.basename(arg, ext)}_dbk_${Date.now()}${ext}`
  ),
  result
)

console.log('完了！')
```

冒頭、末尾の部分はファイルの読み書きやコマンドの引数を読み込んでいるパートなので、実際にBabelを使っている部分は**わずか7行**です。逆に言うと、たったそれだけの行数で書き換え処理が出来てしまうBabelさんすげえ。

#### parse

個別に簡単に解説します。まずは`parse`の部分。

```javascript
const parse = require('@babel/parser').parse
const code = fs.readFileSync(filePath, { encoding: 'utf-8' })
...
const ast = parse(code)
```
codeにはjavascriptの文章がぶち込まれています。これを構文木解析するわけです。
例えば先ほどの上杉謙信塩送りプログラムは、こんな風にバラバラにされます。

```javascript:ast
Node {
  type: 'File',
  start: 0,
  end: 111,
  loc:
   SourceLocation {
     start: Position { line: 1, column: 0 },
     end: Position { line: 7, column: 1 } },
  errors: [],
  program:
   Node {
     type: 'Program',
     start: 0,
     end: 111,
     loc: SourceLocation { start: [Position], end: [Position] },
     sourceType: 'script',
     interpreter: null,
     body: [ 
      Node {
        type: 'FunctionDeclaration',
        start: 0,
        end: 55,
        loc: SourceLocation { start: [Position], end: [Position] },
        id:
        Node {
          type: 'Identifier',
          start: 9,
          end: 19,
          loc: [SourceLocation],
          name: 'isSendSalt' },
        generator: false,
        async: false,
        params: [ [Node] ],
        body:
        Node {
          type: 'BlockStatement',
          start: 27,
          end: 55,
          loc: [SourceLocation],
          body: [Array],
          directives: [] } },
    Node {
      type: 'IfStatement',
      start: 57,
      end: 111,
      loc: SourceLocation { start: [Position], end: [Position] },
      test:
      Node {
        type: 'CallExpression',
        start: 60,
        end: 78,
        loc: [SourceLocation],
        callee: [Node],
        arguments: [Array] },
      consequent:
      Node {
        type: 'BlockStatement',
        start: 79,
        end: 111,
        loc: [SourceLocation],
        body: [Array],
        directives: [] },
      alternate: null 
    } ],  
  directives: [] },
  comments: [] }
```

「うわっ」と思うかもしれませんが、ここで注目して欲しいのが`type`の項目です。ここを読むと、`'Identifier'`, `'IfStatement'`などの文字が見て取れます。
そう、これはどのような要素でプログラムが構成されているか、タグ付けをしてくれているんです。
関数の宣言部分であれば、`'FunctionDeclaration'`とタイプ分けされています。

一つひとつのNodeは要素ごとの情報をまとめた塊になっています。例えば`loc`には開始/終了の行と列の情報が入っています。
様々な情報を内包している形で分解するから、また元に戻せるようになっているんですね。

#### traverse

続いて`traverse`です。

```javascript
const traverse = require('@babel/traverse').default
...
traverse(ast, {
  FunctionDeclaration: (path) => {
    path.node.id.name = 'donBuRakko'
  }
})
```

`traverse([parseしたJS], {type名: (path) => {処理}})` という形で記述が出来ます。
ここでは、 type が `FunctionDeclaration`, つまり関数宣言をしている箇所だけ、名前を`donBuRakko`に変更してください、という指示を出しています。
traverseを使うメリットは再帰的に変換をしてくれる点。いちいち再帰処理を書かなくてもよくなるのです。

#### generate

そして最後に`generate`。これはめちゃめちゃ簡単です。

```javascript
const generate = require('@babel/generator').default
...
const result = generate(ast).code
```

`generate([parseしたJS]).code`でOK。これで元に戻るというわけです。

## Babel Tooling の活用法

今のままでは、ただ単に嫌がらせをするだけのアプリです。
と言うことで、この機能を使うと何が良いのか、少し真面目なことを書いて終わりにしようと思います。

### デバッグ用関数の一括削除

例えば、デバッグ用に `debug()`と言う関数を作成していたとします。

```javascript
const debug = (str) => {
  console.log(`[Debug] ${str}`)
}

let name = '太郎'

debug(name)

name = '二郎'

debug(name)
```

本番環境に持って行くときは、もちろん必要ないわけです。
そんなときは`path.remove()`を使うことで取り除く処理をすることができます。

```javascript
traverse(ast, {
  ExpressionStatement: (path) => {
    const exp = path.node.expression
    if ('callee' in exp) {
      exp.callee.name === funcName && path.remove()
    }
  }
})
```

先ほどのリポジトリにある `src/rm_func.js` を実行してみてください。

```
# node rm_func.js [Filepath] [Function Name]
node rm_func.js ./resource/ex_2.js debug
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/6c3ffcec-7ef3-82d0-e6a9-ab3b26f29148.png)

`debug()`が...

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/241647/6c3f12de-cc1f-da27-e4f3-5e2a0cb034ae.png)

消えました！！


### 命名規則の追加

また、冒頭に紹介した嫌がらせアプリもきちんと応用ができます。
一番最初に説明したのは、関数を無条件に`donBuRakko()`に書き換えるものでしたが、この仕組みを応用すると、

- 戻り値が`boolean`だった場合、関数名は`is`から始める

などのルールもチェックさせることができるようになります。

## つまり

つまり、**オリジナルのLinterが作れる**のです！

これを知ったとき、私かなり感動しました。

ESLintなどでは面倒が見れない自分独自のルールを、簡単に構築・設定することができます。
Babelをうまく活用して行くことで、効率化が図れる。そんな可能性を感じてワクワクしています。

ということで、**皆さんもぜひBabel Toolingを活用してみてください！！！**

.
.
.
 
はぁ〜あ、腰が疲れたなあ。(チラッ

## 関連リンク
- 過去にLTした時の資料
    - https://speakerdeck.com/cha1ra/introduction-of-babel
- 今回のリポジトリ
    - https://github.com/cha1ra/dbk_babel_ex

---
明日の担当は[ikegam1](https://qiita.com/ikegam1)さんです。予定ではWordpress関連のトピックとのこと。楽しみですね！