Node-RED 2.0の新機能: Flow Linter

## はじめに: Flow Linterとは

Node-REDは、ブラウザ画面上でノードをつなげていくことで簡単にフローが開発できます。しかし、グラフィカルなプログラミングは記述の自由度が高い反面、パッと見てわからないバグが潜んでいたり、複数の開発者で共同作業をする際のコーディング規約を守らせることがむずかしいという問題があります。

他のプログラミング言語では、Cに対するlint、Javascriptに対するESLint、Javaに対するCheckstyleなど、記述したコードを静的に解析してバグの発見やコーディング規約の遵守状況をチェックするツールがあります。これらのツールと同様に、Node-REDのフローに対して静的解析を行うツールが[Flow Linter](https://github.com/node-red/nrlint)です。私も微力ながら開発に携わっています。

Flow Linterは、Node-RED 2.0から利用可能となりました。ただし、Node-RED本体には同梱されず、Node-RED 1.3から導入された[プラグイン機能](https://qiita.com/k-toumura/items/9131b4a7cbce66cc088b)[^1]を使って追加インストールする形になります。また、静的検証のルール自体も、プラグイン機能をつかって拡張できるようにつくられています。

[^1]: 実際には、Flow Linterを開発する過程でプラグイン機能の必要性がわかり、プラグイン機能が設計・実装されたという経緯になります。

この記事では、Flow Linterの利用方法を説明します。

## インストール方法

前述したとおり、Flow LinterはNode-REDのプラグインとして実装されています。このため、Flow Linterを利用するためには改めてインストール作業が必要です。ノードのインストールはパレットマネージャが利用できますが、いまのところプラグインはコマンドラインからのみインストールできます。

利用しているNode-RED環境のユーザディレクトリ(`~/.node-red/`など)で、`npm install`コマンドを実行することでプラグインのインストールが行えます。

```sh
% cd ~/.node-red
% npm install nrlint
```
そして、Flow Linterの設定ファイルのひな型をカレントディレクトリに生成するために、`npx`経由で`nrlint`コマンドを実行します。

```sh
% npx nrlint --init > .nrlintrc.js
```

これで、`~/.node-red/.nrlintrc.js`というファイルが生成されます。このファイルには、静的検証する内容のデフォルトのルールが記載されています。[^2]

```javascript
module.exports = {
    "rules": {
        "align-to-grid": true,
        "max-flow-size": true,
        "no-duplicate-http-in-urls": true,
        "no-loops": true,
        "no-overlapping-nodes": true,
        "no-unconnected-http-nodes": true,
        "no-unnamed-functions": true,
        "no-unnamed-links": true,
        "function-eslint": {
            "config": {
                "env": {
                    "es2021": true
                },
                "parserOptions": {
                    "ecmaVersion": 12
                },
                "rules": {
                    "constructor-super": "error",
                    //...省略
                    "valid-typeof": "error"
                }
            }
        }
    }
}

```
[^2]: 上記は最新のmasterブランチのnrlintで生成したもので、v1.0.4以前では`rules`の部分の記述がありません。最新版がnpmにpublishされるまでしばらくお待ちください。


次に、この設定ファイルをNode-REDの設定として読み込ませるために、`~/.node-red/settings.js`の末尾に下記内容を追記します。

```javascript
//...省略
    //    *   - code : if result is false, the HTTP error status to return
    //    *   - reason: if result is false, the HTTP reason string to return
    //    */
    //},
    nrlint: require("./.nrlintrc.js"), // この行を追記
}
```

この設定を行ったうえでNode-REDを起動させると、サイドバーに新たな「Linter」というタブが現れます。

![サイドバー内のFlow Linterのタブ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/e96e0b1d-5b04-05d6-5e1a-612dd9ad35e8.png)
![サイドバー内のFlow Linterのタブ](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/93df19c4-3635-8db8-a3c8-8418f93b8ee6.png)

## Editorからの使い方

まず、初期設定のまま使ってみましょう。
ここでは、フローが空の状態であると仮定します。ここで、functionノードをワークスペース上に配置してみましょう。

![functionノードを配置](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/49c9261a-91b2-7e6f-f779-76c42aabc7fe.png)

すると、いくつかの警告がlinterタブ内に表示されます。

- Node not aligned to grid: グリッドからノードがずれている。ずれていると見た目が悪いので修正することをお勧めします。
- Function node has no name: Functionノードに名前がついていない。どのような機能があるノードなのかが見た目でわからないので、適切な名前をつけることをお勧めします。

また警告対象となったノードにも「!」マークがつき、lintタブやワークスペース下にはエラーと警告の個数が表示されています。

次にfunctionノードを開いて、Javascriptのコードに細工をしてみましょう。

![未使用の変数](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/bdb6b71d-d34b-888d-f7ee-30b7d0194ed7.png)

この変更を加えると、新たに下記のエラーが出現します。

- 'a' is defined but never used: 変数aが定義されているがどこにも使われていない。

このように、フローに変更を加えるとフローの静的検証が実行され、潜在的なエラーの発見やフローの書き方の統一に役立ちます。

この例ではフローに1つのノードしかないため、エラー表示とノードの対応が明白ですが、エラーに対応するノードがわからないときはエラー表示内にあるノードのIDをクリックしてください。対応するノードが点滅します。

また、エラーや警告を一時的に見逃してほしいときは、各エラー/警告表示の右上のメニューから"ignore for node(group/flow)"を選んでください。

ここで出てきた警告は`no-unnamed-functions`,`aligh-to-grid`と`function-eslint`ルールによるものです。現在Flow Linterには下記のルールが実装されています。

### グリッドからずれている(`align-to-grid`)

![align-to-grid](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/076ede09-60bc-8ca2-628a-2fdf31e26358.png)

ノードがグリッドからずれていると見た目が悪いので、グリッドに合わせてください。Node-REDの設定で"Snap to grid"にチェックが入っていれば、グリッドからずれることはあまりないと思います。


### 1フローに含まれるノードが多すぎる(`max-flow-size`)

![max-flow-size](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/9ab2a6e3-03cc-ddb1-63be-91e31efe7ed3.png)

一つのフローに含まれるノードが多いと、コードがわかりづらくなります。サブフローをつかったり、機能別に別のフローに分けることでコードがわかりやすくなります。「多い」の閾値は、初期値で100ノードです。

### HTTP inノードのパス名が重複している(`no-duplicate-http-in-urls`)

![no-duplicate-http-in-urls](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/d676945a-f6bc-e017-9555-704fd292a893.png)

1つのインスタンス上に存在するHTTP inノード間でパス名が重複していると、そのURLへのアクセスがどのノードで処理されるのかがわからなくなります。必ず異なるパスを使用してください。


### 無限ループの可能性(`no-loops`)

![no-loops](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/bef2e0cb-668e-3a4c-1e5a-9748ce2627da.png)

フローがループしています。もちろん、正当なループもありますので、もし誤検知していたらそれらの警告を無視するよう"ignore for flow"等としてください。

### ノードの重なり(`no-overlapping-nodes`)

![no-overlapping-nodes](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/9604fc49-462b-0832-30e6-a5aceade9949.png)

ノードが重なっているとノード名などが読みづらいので、適切に位置を調整してノードが見えるようにしましょう。

### HTTP In/Responseノードが対応していない(`no-unconnected-http-nodes`)

![no-unconnected-http-nodes](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/b4c24f82-9aa2-e6f9-e66a-7eaf7e72a901.png)

HTTP Inノードの先にはHTTP Responseノードを置く必要があります。もしこれが守られていないと、HTTPリクエストを受けたときに返答が送られなくなります。正しく接続しておきましょう。ただし、Flow Linterはフローのトポロジー的な観点でのみ対応関係を判定しているので、このチェックが通ったからと言ってすべての場合でHTTP InノードのメッセージがHTTP Responseノードのいずれかに届くことを保証するものではないです。

### Functionノードに名前がない(`no-unnamed-functions`)

![no-unnamed-functions](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/cc2d1126-925d-4618-7d4d-169f103844a4.png)

Functionノードはメッセージに対して任意の処理を記述できるので、適切な名前をつけないとフローを見たときに何をするのかがわかりません。処理内容がわかるような名前をつけましょう。

### Linkノードに名前がない(`no-unnamed-links`)

![no-unnamed-links](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/29b76734-0d87-09a4-62c3-59f4e951e8e8.png)

Linkノードは、マウスオーバーによって対応するLinkノードを表示することができますが、フローが込み入ってきたりフローを跨ってリンクをしている場合はどのLinkノードに対応しているかがわかりづらくなります。ノードに適切な名前を付けて、どことつながっているのかを明示してください。

### Functionノード内のJavascriptコードに関する警告(`function-eslint`)

![function-eslint](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/b4c31394-56e2-ddd3-a250-34d33129293f.png)

Functionノード内のコードを[ESLint](https://eslint.org/)のルールに従ってチェックします。初期設定では、[ESlintで`"extends": "eslint:recommended"`で有効になるルール](https://eslint.org/docs/rules/)が設定されています。ESLintの各種ルールとその設定に関する設定に関しては、ESLintのドキュメントを参照してください。

## 設定方法

ここまでに述べたルールは、設定によって無効化あるいは細かい調整ができます。Linterタブの右上の「歯車」ボタンを押すと、設定画面が出てきますので、ルールの有効化/無効化、フローのサイズやESLintの設定などは、この画面から変更してください。

![設定画面](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/889500/10ec1d1c-e34f-4045-4a2d-47dc5a09d4b7.png)

## コマンドラインからの使い方

Flow Linterは、エディタからだけではなくコマンドラインからも呼び出すことができます。Flow Linterをインストールしたディレクトリ(`~/.node-red)にて、下記の形で呼び出すことができます。

```sh
% npx nrlint flows.json
╔══════════════════╤══════════╤═══════════════════════╤══════════════════╗
║ Object ID        │ Severity │ Message               │ Rule             ║
╟──────────────────┼──────────┼───────────────────────┼──────────────────╢
║ 92bc4ebe84d23364 │ warn     │ Link node has no name │ no-unnamed-links ║
╚══════════════════╧══════════╧═══════════════════════╧══════════════════╝
✖ 1 problems (0 errors, 1 warnings)

%
```

何らかのエラーか警告があった場合は、エラーコード1が返ってきますので、これを使って「フローを検査して、エラーがあったらコミットしない」などの処理が実現できます。

## 終わりに

本記事では、Node-REDのフローの静的検証ツールであるFlow Linterについて解説しました。冒頭で述べた通り、ここで述べたルール自体もプラグインとして拡張可能です。機会があれば、新たなルールプラグインの開発方法などについても記事にしたいと考えています。また、新たなルールに関するアイデアも募集していますので、[Node-REDのフォーラム](https://discourse.nodered.org/t/request-for-ideas-linter-rules/45932)などにご意見をお寄せいただければ幸いです。

## Node-RED 2.0の新機能紹介
- [Node-RED 2.0の新機能: 概要紹介](https://qiita.com/HiroyasuNishiyama/items/ff8f44cec8c6ffb4f525)
- [Node-RED 2.0の新機能: フローデバッガ](https://qiita.com/HiroyasuNishiyama/items/5a416762fb0024b7e403)
- Node-RED 2.0の新機能: Flow Linter (本記事)

*Node-RED: Node-REDは，OpenJS Foundationの米国およびその他の国における登録商標または商標である。*

*JavaScript, Java: JavaScriptおよびJavaは、Oracleの米国およびその他の国における登録商標または商標である。*

*Checkstyle: Checkstyle は、Free Software Foundation, Inc. の登録商標もしくは商標である。*