Generator が生成するコードの不自然な位置のコメントの比較 JavaScript AST API

- ジェネレーターは、関数の引数の中間などのコメントを移動させることがある
- ジェネレーターによっては、不自然な位置のコメントを失うことがある
- ジェネレーターは、互換性のあるパーサーを使用する必要がある

## 使用する JavaScript コード

すべての単語の間にコメントを入れている。
最初の `//a` は、テストコードが動作しているかを確かめるため。

### コメントあり

```js
//a
/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/
```

### コメントなし

```js

function fn(x,y){}
```

## ジェネレーターが生成したコード

### acorn 8.7.0 + astravel 0.6.1 + astring 1.8.1

ソース:

```js
let acorn = require("acorn");
const { attachComments } = require('astravel')
const astring = require('astring')

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

const comments = []

var ast = acorn.parse(code, {
  ecmaVersion: 2020,
  onComment: comments,
})

attachComments(ast, comments)

var formattedCode =
  astring.generate(ast, {
    comments: true,
  })

console.log(formattedCode)
```

出力:

```js
// a
/*b*/
function fn(x, y) {
  /*c*/
  /*d*/
  /*e*/
  /*f*/
  /*g*/
  /*h*/
  /*i*/
  /*j*/
}
/*k*/

```

### meriyah 4.2.1 + astravel 0.6.1 + astring 1.8.1

ソース:

```js
const { parse } = require('meriyah')
const { attachComments } = require('astravel')
const astring = require('astring')

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`
const comments = []

ast = parse(code, {
  ranges: true,
  onComment: comments,
})

attachComments(ast, comments)

var formattedCode =
  astring.generate(ast, {
    comments: true,
  })

console.log(formattedCode)
```

出力:

```js
// a
/*b*/
function fn(x, y) {
  /*c*/
  /*d*/
  /*e*/
  /*f*/
  /*g*/
  /*h*/
  /*i*/
  /*j*/
}
/*k*/

```

### esprima 4.0.1 + escodegen 2.0.0

astravel で生成したコメントは、
escodegen で使用されないため、
esprima をパーサーに使用した。

一部のコメントが生成されなかった。

ソース:

```js
var esprima = require('esprima')
const es = require('escodegen')

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

let ast = esprima.parse(code, {range: true, tokens: true, comment: true})
ast = es.attachComments(ast, ast.comments, ast.tokens)

var formattedCode =
  es.generate(ast, {
    comment: true,
  })

console.log(formattedCode)
```

出力:

```js
//a
/*b*/
function fn(x, y)
    /*i*/
    {
    }    /*k*/
```

### esprima 4.0.1 + escodegen 2.0.0 + @babel/generator 7.17.9

Meriyah + astravel で生成したASTでは、
@babel/generator でコメントが生成されないため、
esprima をパーサーに使用した。

esprima のコメントは @babel/generator では使用されないため、
escodegen を使ってコメントを抽出する。

@babel/generator で生成することで、
escodegen でもコメントを生成することができた。
ただし、jだけ出力されていない。

ソース:

```js
var esprima = require('esprima')
const es = require('escodegen')
const generate = require('@babel/generator').default

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

let ast = esprima.parse(code, { range: true, tokens: true, comment: true })
ast = es.attachComments(ast, ast.comments, ast.tokens)

ast = { type: 'File', sourceType: 'script', program: ast }

var formattedCode =
  generate(ast).code

console.log(formattedCode)
```

出力:

```js
//a
//b

function //c
fn //d
( //e
x //f
, //g
y //h
) //i
{} //k
```

### uglify-js 3.15.4

元のコードに近いが、iが出力されていない。
ただし、[コメントが失われる場合](https://github.com/mishoo/UglifyJS#keeping-copyright-notices-or-other-comments)がある。
その場合は、オプションに `compress: { unused: false }` を追加すると保存される。

引数の変数名が変わらないよう、 `mangle: false` をオプションに追加している。

ソース:

```js
var UglifyJS = require('uglify-js')

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

var formattedCode =
  UglifyJS.minify(code, {
    mangle: false,
    output: {
      comments: true
    },
  }).code

console.log(formattedCode)
```

出力:

```js
//a
/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/){/*j*/}/*k*/
```

### terser 5.12.1

terser は uglify-es のフォーク。
結果は uglify-js と同様。

ソース:

```js
const { minify } = require('terser')

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

async function asyncCall() {
  var result = await minify(code, {
    mangle: false,
    output: {
      comments: true
    },
  });
  console.log(result.code);
}

asyncCall();
```

出力:

```js
//a
/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/){/*j*/}/*k*/
```

### prettier 2.6.2

全然きれいじゃない（コードのせい）。

ソース:

```js
const prettier = require("prettier")

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

var formattedCode =
  prettier.format(code, { parser: "babel" })

console.log(formattedCode)
```

出力:

```js
//a
/*b*/ function /*c*/ fn /*d*/(/*e*/ x /*f*/, /*g*/ y /*h*/) /*i*/ {
  /*j*/
} /*k*/

```

### sucrase 3.21.0

コード:

```js
const {transform} = require('sucrase')

const code = `//a\n/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/`

var formattedCode =
  transform(code, {transforms: ["typescript", "imports"]}).code

console.log(formattedCode)
```

出力:

```js
"use strict";//a
/*b*/function/*c*/fn/*d*/(/*e*/x/*f*/,/*g*/y/*h*/)/*i*/{/*j*/}/*k*/
```