nodejsでtextlintを動かしてみる


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/330873/26265d02-7ccf-48af-0ccb-597c93f326ec.png)

[青空文庫：蟹工船](https://www.aozora.gr.jp/cards/000156/card1465.html) よりサンプル利用

## 概要
[textlintをnode\.jsで動かす \- Qiita](https://qiita.com/k8uwall/items/f6047a3d4758144a94cb)
を
[俺俺textlintルールを検討してみた \- Qiita](https://qiita.com/k8uwall/items/3c29ef2dce153e13bd1d)
で書き直し

## 構成

```
textlint_app/  // フォルダー名をtextlintにすると衝突するので注意
  + app.js
  + .textlintrc
  + views/
      + index.ejs
```




```js:app.js
// vim:set ts=2 et:
// https://qiita.com/chenglin/items/5e563e50d1c32dadf4c3 express.jsのcors対応
const TextLintEngine = require('textlint').TextLintEngine;
const express = require('express');
const cors = require('cors')
const bodyParser = require('body-parser');
const app = express();
const path = require("path");

// 他からAPIリクエストできるように許可
app.set("view engine", "ejs");

// postデータのjsonをパースするおまじない
app.use(bodyParser.urlencoded({
    extended: true
}));
app.use(bodyParser.json());

// allow cors
app.use(cors());

// 8080番ポートで待ちうける
let listen_port = 8080
app.listen(listen_port, () => {
    console.log('Running at Port %i...', listen_port);
});

app.post('/api/textlint', (req, res, next) => {
    const req_text = req.body.text;
    const engine = new TextLintEngine({
        configFile: path.join(__dirname, ".textlintrc"),
    });
    engine.executeOnText(req_text).then(results => {
        res.json({
            messages: results[0].messages
        });
    });
});

app.get("/", function (req, res) {
  res.render("index.ejs");
});

// その他のリクエストに対する404エラー
app.use((req, res) => {
    var url = req.protocol + '://' + req.headers.host + req.url;
    console.log(url);
    res.sendStatus(404);
});
```

```json:.textlintrc
{
    "rules": {
        "preset-ja-technical-writing": {
            "ja-no-mixed-period": false, // 「。」のつけ忘れのチェックを除外
        },
        "preset-ja-spacing": true,
        "no-start-duplicated-conjunction": {
            "interval" : 2 // interval of sentences
        },
        "no-surrogate-pair": true,
        "no-mixed-zenkaku-and-hankaku-alphabet": true,
        "ja-hiragana-fukushi": true,
        "ja-hiragana-hojodoushi": true,
        "@textlint-ja/textlint-rule-no-insert-dropping-sa": true,
        "prefer-tari-tari": true,
        "@textlint-ja/no-synonyms": true,
        "ja-no-orthographic-variants": true,
        "use-si-units": true,
        "jis-charset": true,
        "no-hoso-kinshi-yogo": true,
        "ja-no-inappropriate-words": true,
    }
}
```

※ホントはJSONに//でコメント入れてはいけない

```js:views/index.js
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css"
          integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.6.0/jquery.min.js"></script>
    <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js"
        integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM"
        crossorigin="anonymous"></script>

</head>
<body>

<form>
<div class="form-group">
    <label for="lint_textarea">Example textarea</label>
    <textarea class="form-control" id="lint_textarea" rows="10" placeholder="Write something here..."></textarea>
</div>
</form>

<div>
      <p><strong>lint結果</strong><p>
      <ul id="textlint_output"></ul>
</div>

<script type="text/javascript">

(function () {
    'use strict';

    $('#lint_textarea').on('input',function () {
        let text = $('#lint_textarea').val();
        let textData = JSON.stringify(
            {
                'text': text,
            });
        let textlint_url = location.protocol + "//" + location.host + "/api/textlint";
        $.ajax({
            type: 'POST',
            url: textlint_url,
            data: textData,
            contentType: 'application/json',
        }).done(function (data, textStatus, jqXHR) {
            console.log(textStatus);
            // https://qiita.com/georgeOsdDev@github/items/34197e63d0fad307fba6
            $("#textlint_output").empty();
            let lines = text.split('\n');
            data.messages.forEach(function(m){
                let li = document.createElement('li');
                $(li).text(m.line + "行目" + m.column + "文字目 [" + m.ruleId + "]: <" + m.message + ">「" + lines[m.line -1] + "」")
                $("#textlint_output").append(li);
            });
        }).fail(function (jqXHR, textStatus, errorThrown) {
            console.log("failed");
        });
    });
}());
</script>

</body>
</html>
```


```sh
$ cd textlint_app
$ npm init --yes
$ npm install textlint \
              textlint-rule-preset-ja-technical-writing \
              textlint-rule-preset-ja-spacing \
              textlint-rule-no-start-duplicated-conjunction \
              textlint-rule-no-surrogate-pair \
              textlint-rule-no-mixed-zenkaku-and-hankaku-alphabet \
              textlint-rule-ja-hiragana-fukushi \
              textlint-rule-ja-hiragana-hojodoushi \
              @textlint-ja/textlint-rule-no-insert-dropping-sa \
              textlint-rule-prefer-tari-tari \
              @textlint-ja/textlint-rule-no-synonyms sudachi-synonyms-dictionary \
              textlint-rule-ja-no-orthographic-variants \
              textlint-rule-use-si-units \
              textlint-rule-jis-charset \
              textlint-rule-no-hoso-kinshi-yogo \
              textlint-rule-ja-no-inappropriate-words
$ npm install express \
              ejs \
              cors
$ node app.js
Running at Port 8080...
```


[Browser上でtextlintを実行する \- Qiita](https://qiita.com/georgeOsdDev@github/items/34197e63d0fad307fba6)
[textlint\-browser\-runner/index\.html at master · mobilusoss/textlint\-browser\-runner](https://github.com/mobilusoss/textlint-browser-runner/blob/master/packages/textlint-browser-runner/example/index.html)