TypeScriptでASTのプラグインを書いてs2sで実行したメモ


## 動作環境

* windows10
* vagrant1.9.7
* virtualbox5.1.26
* ubuntu-16.04
* Docker version 17.09.0-ce, build afdb6d4
* docker-compose version 1.17.1, build 6d101fb

## ASTのサンプル

[簡単AST入門](https://rabbit-house.tokyo/books/javascript-ast/)の、
二項演算子をすべて'*'に変えるプラグインを試してみる。
PluginArgsは、@typesで配布されていなかったので、babelのソースコードを読みながら定義を自作。

[src/PluginArgs.d.ts](https://github.com/hibohiboo/develop/blob/77bf9c2dbb7361ddb1fa928eb2c1afe05755e052/tutorial/lesson/s2s-plugins-ts/src/PluginArgs.d.ts)


```ts:src/test.ts
import { PluginObj, transform } from 'babel-core';
import { NodePath } from 'babel-traverse';
import { BinaryExpression } from 'babel-types';
import PluginArgs from './PluginArgs';

const src: string = 'var a = 1 + 1;';

const plugin = ({ types:t }: PluginArgs): PluginObj => ({
  visitor:{
    BinaryExpression: (nodePath: NodePath<BinaryExpression>) => {
      if (nodePath.node.operator !== '*') {
        const newAst = t.binaryExpression('*', nodePath.node.left, nodePath.node.right);
        nodePath.replaceWith(newAst);
      }
    },
  },
});

const { code } = transform(src, { plugins: [plugin] });
console.log(code);
```

tscでトランスパイルすると以下のようになる。

```js:test.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var babel_core_1 = require("babel-core");
var src = 'var a = 1 + 1;';

var plugin = function (_a) {
    var t = _a.types;
    return ({
        visitor: {
            BinaryExpression: function (nodePath) {
                if (nodePath.node.operator !== '*') {
                    var newAst = t.binaryExpression('*', nodePath.node.left, nodePath.node.right);
                    nodePath.replaceWith(newAst);
                }
            }
        }
    });
};
var code = babel_core_1.transform(src, { plugins: [plugin] }).code;
console.log(code);
```

`node test.js`で確認したところ、正常に動作した。

## s2sのプラグインにする。

```ts:src/babel-plugin-test/index.ts
import { PluginObj } from 'babel-core';
import { NodePath } from 'babel-traverse';
import { BinaryExpression } from 'babel-types';
import PluginArgs from '../PluginArgs';

const plugin = (args: PluginArgs): PluginObj => {
  if (args === undefined) { return { visitor:{} }; }
  const { types:t } = args;
  return {
    visitor:{
      BinaryExpression: (nodePath: NodePath<BinaryExpression>) => {
        if (nodePath.node.operator !== '*') {
          const newAst = t.binaryExpression('*', nodePath.node.left, nodePath.node.right);
          nodePath.replaceWith(newAst);
        }
      },
    },
  };
};

export default plugin;
```
`if (args === undefined) { return { visitor:{} }; }`の部分は、s2sはpluginを1度呼んだ後、
argsなしでもう一度呼んでいるという動きをしていたため、エラー回避用に挿入。
 バグ？

トランスパイルすると以下のようになる。

```js:babel-plugin-test/index.js
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var plugin = function (args) {
    if (args === undefined) {
        return { visitor: {} };
    }
    var t = args.types;
    return {
        visitor: {
            BinaryExpression: function (nodePath) {
                if (nodePath.node.operator !== '*') {
                    var newAst = t.binaryExpression('*', nodePath.node.left, nodePath.node.right);
                    nodePath.replaceWith(newAst);
                }
            },
        },
    };
};
exports.default = plugin;
```

設定ファイルを以下のようにしてプラグインを読み込む。

```js:s2s.config.js
var test = require('./babel-plugin-test').default;

module.exports = {
  watch: './**/*.ts',
  plugins: [
    {
      test: /.*.ts$/,
      plugin: [test],
    },
  ],
}
```

[この時点のソース](https://github.com/hibohiboo/develop/tree/77bf9c2dbb7361ddb1fa928eb2c1afe05755e052/tutorial/lesson/s2s-plugins-ts)

テスト用に以下のファイルを作成して保存したところ、無事に書き換えが発生した。

```ts:test/src/test.ts
let a = 1 + 1;
```
## ※追記  テスト

```ts:babel-plugin-test/index.test.ts
import plugin from '.';
import * as pluginTester from 'babel-plugin-tester';

pluginTester({
  plugin,
  tests: [
    {
      title: 'snapshot test',
      code: `var a = 1 + 1`,
      snapshot: true
    },
    {
      title: 'no change',
      code: `var a = 1 * 1;`,
      output: `var a = 1 * 1;`,
    },
  ],
});
```

`yarn run jest -u`でスナップショットの作成。
`yarn run jest`でテスト。

[この時点のソース](https://github.com/hibohiboo/develop/tree/52839465d912a12690c0479b01b8efd6929399d3/tutorial/lesson/s2s-plugins-ts)

上記のdocker設定が間違っていたので修正 ↓

[修正後ソース](https://github.com/hibohiboo/develop/tree/8a0be702377ecf8c27a92e082530f2bf02c0e102/tutorial/lesson/s2s-plugins-ts)

## 参考

[簡単AST入門][*1]
[s2s-redux-actions-sample][*2]
[babel-plugin-s2s-axios-api][*3]
[s2s][*4]
[babel-plugin][*5]
[astexplorer][*6]
[kamijin-babel-plugins][*7]
[React Redux の SPA を運用して得られた知見と実装例、開発フローもあるよ！][*8]
[BabylonでJavaScript AST事始め][*10]
[@type/babel-traverse][*11]
[@type/babel-core][*12]
[@type/babel-types][*13]
[さよならボイラープレート。s2sによる高速reduxアプリケーション構築][*14]

[*1]:https://rabbit-house.tokyo/ast-book-sample.pdf
[*2]:https://github.com/cndlhvn/s2s-redux-actions-sample
[*3]:https://github.com/cndlhvn/babel-plugin-s2s-axios-api
[*4]:https://github.com/akameco/s2s/tree/master/docs/ja
[*5]:https://sakura.io/blog/2017/12/13/babel-plugins/
[*6]:https://astexplorer.net/
[*7]:https://github.com/kamijin-fanta/babel-plugins
[*8]:https://qiita.com/numanomanu/items/af97312f34cf1388cee6?utm_content=buffer7880a&utm_medium=social&utm_source=twitter.com&utm_campaign=buffer
[*10]:https://qiita.com/erukiti/items/c7bd70758b86fe7956b1
[*11]:https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/babel-traverse/index.d.ts
[*12]:https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/babel-core
[*13]:https://github.com/DefinitelyTyped/DefinitelyTyped/blob/master/types/babel-types/index.d.ts
[*14]:https://qiita.com/akameco/items/e1489c6bbf3439ec6ca4