見出し画像

ECMAScriptの実験的な仕様「ECMAScript throw expressions」を使ってみたいのでESLintを設定してみた

はじめに

こんにちは、SHIFT の開発部門に所属している Katayama です。

今回は、前回の記事(ESLintでParsing error…となった時の対応 parserOptionsを設定する)のまとめに書いていた、実験的な仕様(例えば、2022 年 5 月現在のECMAScript throw expressions)の構文で実装を行うための設定と実際にその構文を利用した実装をやってみたので、それについてみていきたいと思います。 また、実験的な構文で実装している際に出てしまう、ESLint のエラー("Parsing error: ...")の解決方法についてもみていきたいと思います。

Node.js で import・export(ES6 の構文)を使えるように webpack × Babel の設定をやってみたに書かれているように Babel を利用してトランスパイルを行うプロジェクトである事が、本記事に書かれている内容を実践するための前提条件になっています。

今回実装してみようとしている実験的な仕様について

ECMAScript Next compatibility tableに書かれているECMAScript throw expressions(以下の図の青色の四角で囲っている部分)の仕様。

表の内容からBabel7 + core-js3 の組み合わせで変換(トランスパイル)する事で利用可能な仕様だとわかるので、今回実際に利用できるのか?を試してみようと思い、この仕様を選定した。

ECMAScript throw expressions の構文を実際に実装してみる

何もしないとどうなるか?

まず、以下のような babel.config.js の設定でECMAScript throw expressionsに書かれているような実装を行い、Jest によるテストを実行してみるとエラーが起きる。

// babel.config.js
module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: { node: "current" }
      },
    ],
  ],
};
// user.js
export default class User {
  // 省略
  newSyntax(value = throw new TypeError("Argument required")) {
    console.log(value);
  }
}
// exception_user_newSyntax.js
describe("User Model Test : validateSync", () => {
  // 省略
  test("Argument required", () => {
    const exec = () => {
      models.user.newSyntax();
    };
    expect(exec).toThrow();
    expect(exec).toThrowError(/^Argument required$/);
  });
  // 省略
});
[study@localhost node-express]$ yarn test exception_user_newSyntax.js
yarn run v1.22.18
$ jest exception_user_newSyntax.js
 FAIL  __tests__/models/not-mock/exception_user_newSyntax.js
  ● Test suite failed to run

    Jest encountered an unexpected token

    Jest failed to parse a file. This happens e.g. when your code or its dependencies use non-standard JavaScript syntax, or when Jest is not configured to support such syntax.

    Out of the box Jest supports Babel, which will be used to transform your files into valid JS based on your Babel configuration.

    By default "node_modules" folder is ignored by transformers.

    ...省略

    Details:

    SyntaxError: /home/study/workspace/node-express/src/models/user.js: Support for the experimental syntax 'throwExpressions' isn't currently enabled (51:20):

      49 |      }
      50 |
    > 51 |      newSyntax(value = throw new TypeError('Argument required')) {
         |                        ^
      52 |              console.log(value);
      53 |      }
      54 | }

    Add @babel/plugin-proposal-throw-expressions (https://github.com/babel/babel/tree/main/packages/babel-plugin-proposal-throw-expressions) to the 'plugins' section of your Babel config to enable transformation.
    If you want to leave it as-is, add @babel/plugin-syntax-throw-expressions (https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-throw-expressions) to the 'plugins' section to enable parsing.

      ...省略

Test Suites: 1 failed, 1 total
...省略

Node.js(ES6 で実装)で Jest によるテストを実行するための Babel・ESLint の設定とは?
で書いたように、Jest は Babel の設定があればその設定に基づいてテストを実行する前にパース(トランスパイル)=実行可能なコードへの変換が行われるが、今回は上記のようにエラーになってしまっている。

※上記ではテスト実行として Jest を実行して、パースエラー(シンタックスエラー)のログを確認したが、もちろんテストではなく、webpack などで Babel によるトランスパイルをしても同じエラーログを確認できる。詳細は「おまけ」の章を参照。

※Jest の実装に関しては.toThrow(error?)等を参照。

ではどうすればいいか?解決方法

上記の Jest のエラーの中でポイントになるのは以下の 3 点。以下の 3 点にどうすればいいかのヒントが書かれている。

・Jest failed to parse a file.
・SyntaxError: /home/study/workspace/node-express/src/models/user.js: Support for the experimental syntax 'throwExpressions' isn't currently enabled (51:20):
・Add @babel/plugin-proposal-throw-expressions (https://github.com/babel/babel/tree/main/packages/babel-plugin-proposal-throw-expressions) to the 'plugins' section of your Babel config to enable transformation.

"failed to parse a file"の部分からパース(変換)に失敗しているという事が分かり、そのパースに失敗している部分は"newSyntax(value = throw new TypeError('Argument required'))"の部分という事が分かる。

そしてその後のエラーの内容から、変換できるようにするためには Babel の設定に@babel/plugin-proposal-throw-expressionsというものを追加する、と書かれている。

というわけで、"yarn add --dev @babel/plugin-proposal-throw-expressions"でパッケージに追加した上で、babel.config.js を以下のように更新する。

module.exports = {
  presets: [
    [
      "@babel/preset-env",
      {
        targets: { node: "current" }
      },
    ],
  ],
  plugins: ["@babel/plugin-proposal-throw-expressions"], // <- ここを追記
};

更新した後に再度 Jest でテストを実行すると、テストが成功する事が確認できる。

[study@localhost node-express]$ yarn test exception_user_newSyntax.js
yarn run v1.22.18
$ jest exception_user_newSyntax.js
 PASS  __tests__/models/not-mock/exception_user_newSyntax.js
  User Model Test : newSyntax
    ✓ Argument required (11 ms)

Test Suites: 1 passed, 1 total
...省略

※今回ECMAScript throw expressionsを選んだ理由について、「今回実装してみようとしている実験的な仕様について」の章で「Babel7 + core-js3 の組み合わせで変換(トランスパイル)する事で利用可能な仕様である」と書いていた。Jest でテストを実行する前に実は分かっていたのだが、それはECMAScript Next compatibility tableの表の「Babel7 + core-js3」のカラムが緑色(Yes)になっていたので Babel を使えば実験的な仕様でも利用可能であるというが判断できたため。

ESLint の設定をする

何もしないとどうなるか?

上記の「ECMAScript throw expressions の構文を実際に実装してみる」の「何もしないとどうなるか?」と同じ実装して、以下のような.eslintrc.json の設定で eslint を実行してみるとエラーになる。

{
  "root": true,
  "env": { "es2022": true },
  "parserOptions": { "ecmaVersion": 13 },
  "extends": [
    "eslint:recommended",
    "plugin:import/recommended",
    "airbnb-base",
    "plugin:jest/recommended",
    "prettier"
  ],
  "rules": { "no-console": "off" },
  "ignorePatterns": ["/node_modules/", "/dist/", "/openapi/"]
}
[study@localhost node-express]$ yarn lint
yarn run v1.22.18
$ eslint src/

/home/study/workspace/node-express/src/models/user.js
  51:20  error  Parsing error: Unexpected token throw

✖ 1 problems (1 errors, 0 warnings)
...省略

ESLintでParsing error…となった時の対応 parserOptionsを設定するで見たように、今回も"Parsing error ..."になっており、ESLint が構文をパースして解釈できていない事が分かる。

ではどうすればいいか?解決方法

ESLintでParsing error…となった時の対応 parserOptionsを設定するの「解決方法の方法で解決できる理由について」で触れていたSpecifying Parserに書かれている@babel/eslint-parserを利用する。

つまり、"yarn add --dev @babel/eslint-parser"でパッケージを追加した上で、.eslintrc.json を以下のように更新すればいい。

{
  "root": true,
  "env": { "es2022": true },
  "parserOptions": { "ecmaVersion": 13 },
  "parser": "@babel/eslint-parser", // <- ここを追記
  "extends": [
    "eslint:recommended",
    "plugin:import/recommended",
    "airbnb-base",
    "plugin:jest/recommended",
    "prettier"
  ],
  "rules": { "no-console": "off" },
  "ignorePatterns": ["/node_modules/", "/dist/", "/openapi/"]
}

更新した後に再度 eslint を実行すると、パースエラーが出なくなっている事が確認できる。

[study@localhost node-express]$ yarn lint
yarn run v1.22.18
$ eslint src/

/home/study/workspace/node-express/src/models/user.js
  51:2  error  Expected 'this' to be used by class method 'newSyntax'  class-methods-use-this1 problem (1 error, 0 warnings)
...省略

※別の class-methods-use-this ルール違反(クラスメソッドが"this"を利用することを強制する)のエラーが出ているが、それは newSyntax メソッドがクラスのメソッドなのに this を利用していないため。今回はあくまでECMAScript throw expressionsの構文を利用する方法を確認するのが目的だったのでメソッドの中の実装は適当だったが、解決するのであれば(本質的ではないが)"console.log(this)"のように this を利用するコードを追加すれば ESLint のエラーは消える。

newSyntax(value = throw new TypeError('Argument required')) {
	console.log(value);
	console.log(this);
}

ESLint で"Parsing error: ・・・"となった時の対応  parserOptions を設定するでは単に parserOptions に"ecmaVersion": 13 を追記するのみで良かったが、今回パースをしたい構文は実験的な仕様であり、それはどの ECMAScript バージョン(es2022 など)にも含まれていない。そのため、今回は特別なパーサーが必要であり、Babel の設定でパースを行う@babel/eslint-parser を利用した。

※ちなみに、ESLint に@babel/eslint-parserを設定すると、ESLint は Babel でパース(トランスパイル)されたコードに対して構文チェックを実行する事になる。構文チェックの結果がトランスパイル前のコード上の位置で表示されるのは、トランスパイル時に元のコードの位置を記録しているため(以下、公式からの引用)。

ESLint allows for the use of custom parsers. When using this plugin, your code is parsed by Babel's parser (using the configuration specified in your Babel configuration file) and the resulting AST is transformed into an ESTree-compliant structure that ESLint can understand. All location info such as line numbers, columns is also retained so you can track down errors with ease.(ESLint はカスタムパーサーを使用することができます。このプラグインを使うとき、あなたのコードは Babel のパーサーによって解析され(Babel の設定ファイルで指定された構成を使って)、結果の AST は ESLint が理解できる ESTree に準拠した構造に変換されます。行番号や列のような位置情報もすべて保持されるので、エラーを簡単に追跡することができます。)

※追加設定後、eslint のコマンドでチェックした際にはエラーは出なくなったが、VS Code 上では以下の画像のように追加設定後もエラーが出ていた。おそらく VS Code の ESLint の Extension の方に問題があると思われる。

まとめとして

今回は実験的な仕様を利用する方法と、ESLint のエラーの解消方法についてみてきた。ESLintでParsing error…となった時の対応 parserOptionsを設定するの記事で取り上げていた@babel/eslint-parser の使い道も今回の記事で理解を深める事ができ良かった。

おまけ

webpack で Babel によるトランスパイルを実行するとどうなるか?

上記で見てきたような Babel の追加設定を行わずに、Jest ではなく webpack などで Babel によるトランスパイルをするとどうなるか?を見てみると、以下のように Jest の時と同じパースエラーが出る。

[study@localhost node-express]$ yarn build:dev
yarn run v1.22.18
$ webpack watch --node-env=development
asset index.js 3.13 KiB [emitted] (name: index) 1 related asset
./src/index.js 39 bytes [built] [code generated] [1 error]

ERROR in ./src/index.js
Module build failed (from ./node_modules/babel-loader/lib/index.js):
SyntaxError: /home/study/workspace/node-express/src/index.js: Support for the experimental syntax 'throwExpressions' isn't currently enabled (16:26):

  14 | app.use('/api/v1', router);
  15 |
> 16 | function save(filename = throw new TypeError('Argument required')) {
     |                          ^
  17 |  console.log(filename);
  18 | }
  19 |

Add @babel/plugin-proposal-throw-expressions (https://github.com/babel/babel/tree/main/packages/babel-plugin-proposal-throw-expressions) to the 'plugins' section of your Babel config to enable transformation.
If you want to leave it as-is, add @babel/plugin-syntax-throw-expressions (https://github.com/babel/babel/tree/main/packages/babel-plugin-syntax-throw-expressions) to the 'plugins' section to enable parsing.
    at instantiate (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:72:32)
    at constructor (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:358:12)
    at Parser.raise (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:3335:19)
    at Parser.expectPlugin (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:3384:16)
    at Parser.parseMaybeUnary (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:12462:14)
    at Parser.parseMaybeUnaryOrPrivate (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:12284:61)
    at Parser.parseExprOps (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:12291:23)
    at Parser.parseMaybeConditional (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:12261:23)
    at Parser.parseMaybeAssign (/home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:12214:21)
    at /home/study/workspace/node-express/node_modules/@babel/parser/lib/index.js:12172:39

ERROR in
/home/study/workspace/node-express/src/index.js
  16:25  error  Parsing error: This experimental syntax requires enabling the parser plugin: "throwExpressions". (16:25)

✖ 1 problem (1 error, 0 warnings)


node-express (webpack 5.72.1) compiled with 2 errors in 2601 ms

《この公式ブロガーの記事一覧》



執筆者プロフィール:Katayama Yuta
SaaS ERPパッケージベンダーにて開発を2年経験。 SHIFTでは、GUIテストの自動化やUnitテストの実装などテスト関係の案件に従事したり、DevOpsの一環でCICD導入支援をする案件にも従事。 昨年に開発部門へ異動し、再び開発エンジニアに。座学で読み物を読むより、色々手を動かして試したり学んだりするのが好きなタイプ。

お問合せはお気軽に
https://service.shiftinc.jp/contact/

SHIFTについて(コーポレートサイト)
https://www.shiftinc.jp/

SHIFTのサービスについて(サービスサイト)
https://service.shiftinc.jp/

SHIFTの導入事例
https://service.shiftinc.jp/case/

お役立ち資料はこちら
https://service.shiftinc.jp/resources/

SHIFTの採用情報はこちら
https://recruit.shiftinc.jp/career/


みんなにも読んでほしいですか?

オススメした記事はフォロワーのタイムラインに表示されます!