タスクリストの仕様を理解したい


# タスクリストの仕様を理解したい

Markdown におけるタスクリストの仕様について理解したい。

多くの Markdown では、GitHub Flavored Markdown (GFM) による仕様に準拠している。そのため、タスクリストの仕様を理解するには、GFM を読むことになる。

$
\newcommand{\mathsymb}[1]{\mathtt{#1}}
\newcommand{\w}{\mathsymb{W}}
\newcommand{\n}{\mathsymb{N}}
$

<!--
この問題は解決しました！
2022/11/29
:::note info
現在、タスクリストはルーズにするとチェックボックスが壊れてしまいます。

[タスクリストをルーズにするとリストが壊れる · Issue #318 · increments/qiita-discussions](https://github.com/increments/qiita-discussions/issues/318)
:::
-->

## ■ GFM による仕様

タスクリストの仕様は CommonMark にはなく、それを拡張した GitHub Flavored Markdown に記載されている。

> [\# 5.3 Task list items (extension) \- GitHub Flavored Markdown Spec](https://github.github.com/gfm/#task-list-items-extension-)

ここでの仕様の記述量はあまり多くなく、以下の点のみである。

- **タスクリスト項目**は必ずパラグラフから始まる
    - パラグラフとタスクリスト項目マーカーの間には 1 つ以上の空白文字が必要
- **タスクリスト項目マーカー**は任意の数の空白文字、`[`、`x` または `X`、`]` によって構成される
    - これはチェックボックスに変換される
    - `x` または `X` が空白文字の場合、チェックのないチェックボックスとなる
- チェックボックスが相互作用的に動作するか（レンダリング後にクリック等によってチェックボックスの変化を許容するか）は定義しない
    - GitHub の issue では編集無しにクリックでチェックボックスを変更することが出来る
- 任意のネスト

これ以外は、タスクリストも番号なしリストや番号付きリストと同様の仕様になっているようだ。

本記事で利用するリストにおけるさまざまな言葉の定義は以下の記事を参照してほしい。

> [\#言葉の定義 \- CommonMark リストの仕様を理解したい - Qiita](https://qiita.com/Yarakashi_Kikohshi/items/234d67c193a71b72d035#-%E8%A8%80%E8%91%89%E3%81%AE%E5%AE%9A%E7%BE%A9)

ここで、GFM の仕様で登場した言葉を明らかにしておきたい。

```markdown
リストマーカー
↓
~
- [ ] ここは “タスクリスト項目”
  ^^^
  ↑
  タスクリスト項目マーカー

-----

リストマーカー
↓
~
- [x] ここは “タスクリスト項目”
  ^^^
  ↑
  タスクリスト項目マーカー
```

上を見れば明らかなように、リストマーカー幅は $\w=1$ である。リストマーカーを `- [ ]`・`- [x]` だと思って、リストマーカー幅を $\w=5$ と考えてしまうかもしれないが、これは間違いである。

また、リストマーカーは `-` にこだわらず、`+` や `*` でも良い。

## ■ タスクリスト項目マーカー

タスクリスト項目マーカーは、仕様では以下のように記載されていた。

- タスクリスト項目マーカーは任意の数の空白文字、`[`、`x` または `X`、`]` によって構成される
    - これはチェックボックスに変換される
    - `x` または `X` が空白文字の場合、チェックのないチェックボックスとなる

しかし、これは少し誤解の生みやすい記述なのかもしれない。以下の 2 点についてパーサによる実装の違いに影響を受ける可能性がある。

### ▽ 空白文字

「仕様で示されている “空白文字 (Whitespace character)” とは、半角スペースであり、GFM で規定されている [Whitespace character][gfm-wc] ではない」と捉えるパーサがあるようだ。（実際、半角スペース (`U+0020`) は “[space][gfm-space]” と定義されている）

[gfm-wc]: https://github.github.com/gfm/#whitespace-character
[gfm-space]: https://github.github.com/gfm/#space

そのため、半角スペースをタブに変更したときでタスクリストとして機能する場合と機能しない場合がある。実際、[Marked\.js][marked-demo] と markdown-it では挙動が異なる。（markdown-it はデモがないため、[Dillinger\.io][dillinger] 等を利用して確かめてみると良い）

```markdown
- [	] 角括弧の中にタブ
-	[ ] リストの項目インデントがタブ
```

[marked-demo]: https://marked.js.org/demo/?text=-%20%5B%09%5D%20%E8%A7%92%E6%8B%AC%E5%BC%A7%E3%81%AE%E4%B8%AD%E3%81%AB%E3%82%BF%E3%83%96%0A-%09%5B%20%5D%20%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E3%82%A4%E3%83%B3%E3%83%87%E3%83%B3%E3%83%88%E3%81%8C%E3%82%BF%E3%83%96&options=%7B%0A%20%22async%22%3A%20false%2C%0A%20%22baseUrl%22%3A%20null%2C%0A%20%22breaks%22%3A%20false%2C%0A%20%22extensions%22%3A%20null%2C%0A%20%22gfm%22%3A%20true%2C%0A%20%22headerIds%22%3A%20true%2C%0A%20%22headerPrefix%22%3A%20%22%22%2C%0A%20%22highlight%22%3A%20null%2C%0A%20%22langPrefix%22%3A%20%22language-%22%2C%0A%20%22mangle%22%3A%20true%2C%0A%20%22pedantic%22%3A%20false%2C%0A%20%22sanitize%22%3A%20false%2C%0A%20%22sanitizer%22%3A%20null%2C%0A%20%22silent%22%3A%20false%2C%0A%20%22smartypants%22%3A%20false%2C%0A%20%22tokenizer%22%3A%20null%2C%0A%20%22walkTokens%22%3A%20null%2C%0A%20%22xhtml%22%3A%20false%0A%7D&version=master

[dillinger]: https://dillinger.io/

<details>
<summary>パーサによる解釈の違いの比較（折りたたみ）</summary>
<div>

|         Marked\.js          |    Dillinger (markdown-it)    |
| :-------------------------: | :---------------------------: |
| ![marked\.js][marked-image] | ![dillinger][dillinger-image] |

[dillinger-image]: https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/e525b9dd-25f8-f016-1925-56a2560b5ec6.png
[marked-image]: https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/28ecae0c-3eb8-f3cb-bc23-c01c3980d36c.png

</div>
</details>

### ▽ チェックボックス

GFM では、チェックボックス内にチェックを入れる場合、`x` または `X` を入れることが出来る。しかし、`X` を受け付けないパーサもある。

```markdown
- [ ] チェックなし
- [x] `x` によるチェックあり
- [X] `X` によるチェックあり
```

- [ ] チェックなし
- [x] `x` によるチェックあり
- [X] `X` によるチェックあり

Qiita では `X` を利用できないようなので、`x` のみを使おう。

## ■ リストマーカー幅

タスクリストのリストマーカー幅は $\w=1$ である。これを実験的に示してみたい。これは Indented code block をリスト内に含めたとき、顕著に確認することが出来る。

リストマーカー幅が $\w=5$ の場合、リストにネストされた Indented code block は全体として $5+\n+4$ 個のインデントが必要になる。例として、$\n=1$ の場合を考えると 10 つのスペースでインデントする必要がある。

```markdown
- [ ] ここはチェックなし

          Indented by 10 spaces

- [x] ここはチェックあり

          Indented by 10 spaces
```

- [ ] ここはチェックなし

          Indented by 10 spaces

- [x] ここはチェックあり

          Indented by 10 spaces

これを見ると、コードブロック内にインデントが含まれてしまっている。この含まれているスペースは 4 つとなっている。したがって、スペース 6 つのインデントがあれば Indented code block として認識されていることが分かる。ここから Indented code block に必要な 4 つのスペースを引けば、$\w+\n=2$ であることが分かる。（$\n=1$ のときを考えていたので $\w=\n=1$）

したがって、タスクリストのリストマーカー幅は $\w=1$ である。

<!--
タスクリストにネストされる Indented code block は 6 つのインデントによって表現される。

```markdown
- [ ] ここはチェックなし

      Indented by 6 spaces

- [x] ここはチェックあり

      Indented by 6 spaces
```

- [ ] ここはチェックなし

      Indented by 6 spaces

- [x] ここはチェックあり

      Indented by 6 spaces
-->

## ■ 項目インデント

タスクリストの項目インデントは `- [ ]` の後ろではなく、`-` の後ろである。

```markdown
リストマーカー
↓
~
- [ ] ここは “タスクリスト項目”
 ^
 ↑
 リストの項目インデント

-----

リストマーカー
↓
~
- [x] ここは “タスクリスト項目”
 ^
 ↑
 リストの項目インデント
```

`- [ ]`・`- [x]` の後ろをインデントした場合、リストの項目インデントとして認識されず無視される。これはその他の部分に影響しない。

```markdown
- [ ]     どれだけインデントしても無視される
- [x]     どれだけインデントしても無視される
```

- [ ]     どれだけインデントしても無視される
- [x]     どれだけインデントしても無視される

項目インデントは `-` とタスクリスト項目マーカーの間であり、例えば以下のような例から見ることが出来る。

```markdown
-  [ ] N=2 の項目インデント

          Indented by 10 spaces

-   [ ] N=3 の項目インデント

          Indented by 10 spaces
```

-  [ ] $\n=2$ の項目インデント

          Indented by 10 spaces

-   [ ] $\n=3$ の項目インデント

          Indented by 10 spaces

これは、$\n=2$ のとき 3 つのスペースが、$\n=3$ のとき 2 つのスペースが Indented code block に含まれている。このことから、`-` の右にあるスペースは項目インデントとして機能していることが分かる。

## ■ リストの分割

タスクリストは、そのリストマーカーに番号なしリストのリストマーカーと同じマーカーを利用している。そのため、タスクリストと番号なしリストとを連続して記述すると、ルーズなリストとして認識されてしまう。

```markdown
- [ ] ここはチェックなし
- [x] ここはチェックあり

- ここは番号なしリスト
- ここは番号なしリスト
```

- [ ] ここはチェックなし
- [x] ここはチェックあり

- ここは番号なしリスト
- ここは番号なしリスト

基本的な考え方は [\# 連続するリストの分割 \- CommonMark リストの仕様を理解したい][sep-list] と同じ。これを回避するには、間にコメントアウトを挿入する、あるいは、どちらかのリストマーカーを別のものに変更すれば良い。

[sep-list]: https://qiita.com/Yarakashi_Kikohshi/items/234d67c193a71b72d035#-%E9%80%A3%E7%B6%9A%E3%81%99%E3%82%8B%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E5%88%86%E5%89%B2

<!-- [CommonMark リストの仕様を理解したい - Qiita](https://qiita.com/Yarakashi_Kikohshi/items/234d67c193a71b72d035#-%E9%80%A3%E7%B6%9A%E3%81%99%E3%82%8B%E3%83%AA%E3%82%B9%E3%83%88%E3%81%AE%E5%88%86%E5%89%B2) -->

```markdown
- [ ] ここはチェックなし
- [x] ここはチェックあり

+ ここは番号なしリスト
+ ここは番号なしリスト
```

- [ ] ここはチェックなし
- [x] ここはチェックあり

+ ここは番号なしリスト
+ ここは番号なしリスト

タスクリスト側のリストマーカーを変更していても良い。

```markdown
+ [ ] ここはチェックなし
+ [x] ここはチェックあり

- ここは番号なしリスト
- ここは番号なしリスト
```

+ [ ] ここはチェックなし
+ [x] ここはチェックあり

- ここは番号なしリスト
- ここは番号なしリスト

## ■ 番号付きタスクリスト

タスクリストのリストマーカーは `-` が利用される。このマーカーは前述の通り、`+` や `*` でも良い。

驚くべきことに、`1. [ ]`・`1. [x]` や `1) [ ]`・`1) [x]` であってもタスクリストとして機能する。（HTML 的には、`<ul>` でも `<ol>` でも問題ないということなのだろうか）

```markdown
1. [ ] ここは番号付きなタスクリスト (`1.`)
1. [x] ここは番号付きなタスクリスト (`1.`)

1) [ ] ここは番号付きなタスクリスト (`1)`)
1) [x] ここは番号付きなタスクリスト (`1)`)
```

1. [ ] ここは番号付きなタスクリスト (`1.`)
1. [x] ここは番号付きなタスクリスト (`1.`)

1) [ ] ここは番号付きなタスクリスト (`1)`)
1) [x] ここは番号付きなタスクリスト (`1)`)

また、このときのリストマーカー幅は `2` となる。

```markdown
1. [ ] Indented code block を含む番号付きなタスクリスト (`1.`)

       Indented by 7 spaces

1) [ ] Indented code block を含む番号付きなタスクリスト (`1)`)

       Indented by 7 spaces
```

1. [ ] Indented code block を含む番号付きなタスクリスト (`1.`)

       Indented by 7 spaces

1) [ ] Indented code block を含む番号付きなタスクリスト (`1)`)

       Indented by 7 spaces

タスクリスト内に Indented code block をネストする際には、番号付きタスクリストにすると、タスクリスト項目と行頭が揃っていてイイ感じに見える。

## ■ タスクリストの HTML 表示

タスクリストは “リストマーカー＋タスクリスト項目マーカー” で構成されている。

HTML で表示する場合には、[Example 279][ex-279] を参照すると以下のようになっている。（実際はさまざまな形式にパースされている）

[ex-279]: https://github.github.com/gfm/#task-list-items-extension-:~:text=final%20rendered%20document.-,Example%20279,-%2D%20%5B%20%5D

```html
<ul>
<li><input disabled="" type="checkbox"> foo</li>
<li><input checked="" disabled="" type="checkbox"> bar</li>
</ul>
```

ここでのタスクリスト項目マーカーは、`<input disabled="" type="checkbox">`・`<input checked="" disabled="" type="checkbox">` に対応している。多くのパーサでは `<input>` タグはサニタイズされるため、上のような HTML 表示では Markdown に移植することは出来ない。

しかしながら、番号なしリストの HTML 表示と `[ ]`・`[x]` を利用することでタスクリストを表示させることが出来る場合がある。

```markdown
<ul>
<li>[ ] チェックなし</li>
<li>[x] チェックあり</li>
</ul>
```

<ul>
<li>[ ] チェックなし</li>
<li>[x] チェックあり</li>
</ul>

<!-- ![evidence](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/590705/49077c9c-6e6a-4fd5-0d6a-394dbfca4acd.png) -->

これが可能であれば、Markdown 表内にも挿入することが出来る。

```markdown
| タスクリスト                                                |
| :---------------------------------------------------------- |
| <ul><li>[ ] チェックなし</li><li>[x] チェックあり</li></ul> |
```

| タスクリスト                                                |
| :---------------------------------------------------------- |
| <ul><li>[ ] チェックなし</li><li>[x] チェックあり</li></ul> |

多くの場合、HTML タグ内には Markdown 記法を含めることが出来ないが、チェックボックスに限って Markdown 記法を含めることが出来る。

Qiita では可能になっているようですね。ふしぎ :thinking:

## ■ GitLab におけるタスクリスト

GitLab Flavored Markdown (GLFM) におけるタスクリストでは、タスクリスト項目マーカーに `[~]` がある。`[~]` としたタスクは打ち消される。

> [Task lists \- GitLab Flavored Markdown (GLFM) \| GitLab](https://docs.gitlab.com/ee/user/markdown.html#task-lists "Task lists for GLFM")

```markdown
- [x] Completed task
- [~] Inapplicable task
- [ ] Incomplete task
  - [x] Sub-task 1
  - [~] Sub-task 2
  - [ ] Sub-task 3

1. [x] Completed task
1. [~] Inapplicable task
1. [ ] Incomplete task
   1. [x] Sub-task 1
   1. [~] Sub-task 2
   1. [ ] Sub-task 3
```

# 余談

CommonMark のリストの仕様を知っていれば、そんなに難しくないかもしれない。少しの制約とタスクリスト項目マーカーが追加されただけである。

タスクリストにおいて、パラグラフ以外のブロックをリスト項目にできないことは、覚えておいて損はないだろう。

Markdown 表内にタスクリストは含めることが出来ないと考えていたので、意外な方法で含めることが出来ることを発見できておもしろかった（小並感）

# 追記

- 2022/11/29: タスクリストをルーズにしたときチェックボックスが壊れてしまう不具合が解消されました。
- 2023/01/22: GitLab におけるタスクリストについて追記しました。