69
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

ESLint対応物語 ~no-restricted-syntax~

Last updated at Posted at 2017-05-08

むちゃくちゃ間が空いちゃいましたが、明日会社でLTやるので書きますw

今回は**「no-restricted-syntax」=「制限構文を使っちゃだめ」**
というルールの紹介と対応方法をまとめたいと思います。

具体的にはどんなもの?

no-restricted-syntax
このルール自体に制約はなく、オプションで指定した構文を禁止する、といったもののようです。

ちなみに eslint-config-airbnb v14.1.0ではこんな感じで設定されています。

    // disallow certain syntax forms
    // http://eslint.org/docs/rules/no-restricted-syntax
    'no-restricted-syntax': [
      'error',
      {
        selector: 'ForInStatement',
        message: 'for..in loops iterate over the entire prototype chain, which is virtually never what you want. Use Object.{keys,values,entries}, and iterate over the resulting array.',
      },
      {
        selector: 'ForOfStatement',
        message: 'iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.',
      },
      {
        selector: 'LabeledStatement',
        message: 'Labels are a form of GOTO; using them makes code confusing and hard to maintain and understand.',
      },
      {
        selector: 'WithStatement',
        message: '`with` is disallowed in strict mode because it makes code impossible to predict and optimize.',
      },
    ],

何故いけないのか

jsはいろんな書き方ができるけど、同じプログラムの中で何通りも書かれてるとわかりにくいから、制限して統一しようぜー
と言う旨をつたない英語力で読み取りました。(間違ってたら教えてください)

airbnbのルール設定では、for-in、for-of、withとlabeledを禁止に設定しています。
withとlabeledはjsでもイケてない(可読性を下げる)代表みたいな二人なので禁止はわかりやすいです。
for-inも有名で、列挙可能なプロパティを全て取り出してしまうため、意図しないご先祖様のプロパティをも読み込んでしまうというやつです。

でもfor-ofって、ES2015で定義された比較的若い構文じゃないですか!
せっかく覚えたんだから使いたーい!
と思うところなのですが、airbnb的にはこういうことらしいです。

 Why? This enforces our immutable rule. Dealing with pure functions that return values is easier to reason about than side effects.
Use map() / every() / filter() / find() / findIndex() / reduce() / some() / ... to iterate over arrays, and Object.keys() / Object.values() / Object.entries() to produce arrays so you can iterate over objects.

11.1 Don't use iterators.

ざっくり書くと、「だいたいArrayのメソッドとかObjectのstaticメソッド使えば表現できるし、そっちの方がシンプルでわかりやすいじゃん」とのことです。

うん、まぁ、そうか。。。

追記:"for-of"禁止のもっと納得の理由が堂々と記述されていました。lint設定のメッセージより、
「変換に必要なruntimeがちょっと重すぎるから」とのことです。

        message: 'iterators/generators require regenerator-runtime, which is too heavyweight for this guide to allow them. Separately, loops should be avoided in favor of array iterations.',

対応例、for-of-breakの代替

とりあえず今回はこちらの対応方法だけ書いておきます!
forで大変助かるのがbreak、無駄なループをせずに必要な処理だけで済ませることができます。
ですが、最もメジャーな map や forEach は途中で止めることができません。
ではどうするか?ということで、具体的な例を見ながら2つほど方法を紹介します。

NGを見つけ出してbreak -> every

ある配列の中の値が全て5以下であって欲しくて、5より大きいものが見つかったらエラーとしてbreak、と言うパターン。

const data = [
    { value : 1 },
    { value : 2 },
    { value : 6 },
];
let allOK = true;
for (let row of data) {
    if (row.value <= 5) {
        allOK = false;
        break;
    }
}

everyを使います。

const data = [
    { value : 1 },
    { value : 2 },
    { value : 6 },
];
const allOK = data.every((row) => (row.value <= 5));

簡潔!!

OKを見つけ出してbreak -> some

続きまして、ある値を探して、それが見つかった時点でやめたい場合

const routes = [
    { path : '/hoge' },
    { path : '/fuga' },
    { path : '/piyo' },
];
let isMatch = false;
for (let route of routes) {
    if (currentPath === route.path) {
        isMatch = true;
        break;
    }
}

someを使います。

const routes = [
    { path : '/hoge' },
    { path : '/fuga' },
    { path : '/piyo' },
];
const isMatch = routes.some((route) => (currentPath === route.path));

簡潔!!!!

余談ですが、私は上記の例でルーティング設定を取り出すなどしました。

const routes = [
    { path : '/hoge', settings : {/* いろいろ */} },
    { path : '/fuga', settings : {/* いろいろ */} },
    { path : '/piyo', settings : {/* いろいろ */} },
];
let settings = {};
const isMatch = routes.some((route) => {
    if (currentPath === route.path) {
        settings = route.settings;
        return true;
    }
    return false;
});

本来airbnbのルール的には、「for文はループの外の変数を書き換える(=副作用的な動きをする)ので、使わないようにしよう」と言うものですが、上記ではsettingsと言う変数を副作用的に変更してしまっています。
なので結局for文の方が適切なのでは?と個人的には思うのですが、少しでも構文を減らして整えたいというeslint側の目的には沿うので、受け入れることにしました。
世知辛み。

追記:いただいたコメントから、findを使うと綺麗にかけることがわかりました。ありがとうござますm(_ _)m

const routes = [
    { path : '/hoge', settings : {/* いろいろ */} },
    { path : '/fuga', settings : {/* いろいろ */} },
    { path : '/piyo', settings : {/* いろいろ */} },
];
const currentPath = '/piyo';
const route    = routes.find((route) => (currentPath === route.path));
const isMatch  = Boolean(route);
const settings = isMatch ? route.settings : {};

よく見ると

everyはfalseが見つかるまでループ、someはtrueが見つかるまでループなので、実はほとんど変わりません。
for文で書くとどちらもほとんど同じだし、判定文を反転させればどちらにでも入れ替わります。
ただ、falseを見つけたいロジックと、trueを見つけたいロジックでは文脈が変わってくるものです。
ですので適切な方を選んで使うことで、文脈が通り、
より意図がわかりやすい(読みやすい)コードにすることができるのです!!

まとめ

  • no-restricted-syntaxの説明とairbnbでの設定を紹介しました。
  • そのうち for-of-break の代替法を2つ紹介しました。
  • every と some を適切に使うと可読性が上がります。

時間ができたら他のメソッドも紹介したいです。

参考資料

69
42
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
69
42

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?