Vize

テンプレートの安全性と HTML の構造

このルール群では、HTML の挿入や URL の指定、属性と ID の重複、textarea・コンポーネントへのテキストの渡し方、HTML の入れ子を確認します。

外部から受け取った値を表示するときや、リンク・フォームを追加するとき、同じコンポーネントを複数表示するときに確認してください。

たとえば、HTML を挿入せずテキストとして表示する例とフォームの ID を生成して関連付ける例では、悪い例と良い例の差を確認できます。採用するルールだけを掲載された設定で有効にし、vp run lint を実行してください。Vue の全ルールから関連するテンプレートの検査も確認できます。

このページに、各ルールの目的・前提・設定と、省略のない悪い例・良い例をまとめています。変更箇所は行の色で示します。コードをコピーすると、ソースコード全体を取得できます。各例に現在の対応範囲も明記しています。

ルール 例 目的
vue/no-duplicate-attributes 悪い · 良い 同じ要素の属性重複を検出します。
vue/no-textarea-mustache 悪い · 良い textarea 内の mustache を検出し、v-model の使用を勧めます。
vue/no-unsafe-url 悪い · 良い 危険なスキームを含む可能性がある URL 属性やバインディングを検出します。
vue/no-v-html 悪い · 良い XSS を防ぐため、v-html を使用した箇所に警告を出します。
vue/no-v-text-v-html-on-component 悪い · 良い コンポーネントでの v-text / v-html を検出します。
vue/permitted-contents 悪い · 良い HTML の要素ごとのコンテンツモデルを検査します。
vue/use-unique-element-ids 悪い · 良い 静的 ID の代わりに useId() で一意な ID を生成します。

全ルール · ルールのオプション · ESLint 移行対応表 · プロジェクトの検査 · 子への属性の継承

vue/no-duplicate-attributes

同じ要素の属性重複を検出します。

悪い例 · 良い例

既定の重大度: error
プリセット: essential, happy-path, nuxt, ecosystem, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/no-duplicate-attributes": "error"
      }
    },
  },
});
vp run lint

悪い

一つの button に class を二回指定しています。

<template>
  <button class="primary" class="large">Save</button>
</template>

良い

二つのクラスを一つの class 属性にまとめます。

<template>
  <button class="primary large">Save</button>
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール

vue/no-textarea-mustache

textarea 内の mustache を検出し、v-model の使用を勧めます。

悪い例 · 良い例

既定の重大度: error
プリセット: essential, happy-path, nuxt, ecosystem, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/no-textarea-mustache": "error"
      }
    },
  },
});
vp run lint

悪い

textarea の値をバインドせず、要素内のテキスト補間に message を記述しています。

<template>
  <textarea>{{ message }}</textarea>
</template>

良い

v-model で、編集可能な textarea の値と message を関連付けます。

<template>
  <textarea v-model="message"></textarea>
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール

vue/no-unsafe-url

危険なスキームを含む可能性がある URL 属性やバインディングを検出します。

悪い例 · 良い例

既定の重大度: warning
プリセット: essential, happy-path, nuxt, ecosystem, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/no-unsafe-url": "warn"
      }
    },
  },
});
vp run lint

悪い

a の移動先に、JavaScript を実行する javascript: スキームを使っています。

<template>
<a href="javascript:alert(1)">Continue</a>
</template>

良い

移動先を、通常のローカルパス /next に変更します。

<template>
<a href="/next">Continue</a>
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール

vue/no-v-html

XSS を防ぐため、v-html を使用した箇所に警告を出します。

悪い例 · 良い例

既定の重大度: warning
プリセット: essential, happy-path, nuxt, ecosystem, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/no-v-html": "warn"
      }
    },
  },
});
vp run lint

悪い

v-html で content を通常の文字ではなく HTML として扱っています。

<template>
  <article v-html="content" />
</template>

良い

mustache の補間で、HTML を挿入せず content をエスケープしたテキストとして表示します。

<template>
  <article>{{ content }}</article>
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール

vue/no-v-text-v-html-on-component

コンポーネントでの v-text / v-html を検出します。

悪い例 · 良い例

既定の重大度: error
プリセット: essential, happy-path, nuxt, ecosystem, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/no-v-text-v-html-on-component": "error"
      }
    },
  },
});
vp run lint

悪い

コンポーネントに、要素の内容を置き換える v-html や v-text を指定しています。

<template>
  <MyComponent v-html="content" />
  <MyComponent v-text="content" />
</template>

良い

標準の HTML 要素にはディレクティブを使えます。MyComponent にはデフォルトスロットから内容を渡します。

<template>
  <div v-html="content"></div>
  <component is="div" v-html="content" />
  <MyComponent>{{ content }}</MyComponent>
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール

vue/permitted-contents

HTML の要素ごとのコンテンツモデルを検査します。

悪い例 · 良い例

既定の重大度: error
プリセット: happy-path, nuxt, ecosystem, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/permitted-contents": "error"
      }
    },
  },
});
vp run lint

悪い

p にブロック要素を入れたり、tbody を省略したりしています。操作要素の入れ子や、ul の直下に div を置く例もあります。

<template>
  <p><div>block in a paragraph</div></p>
  <table><tr><td>row without tbody</td></tr></table>
  <a href="#"><button type="button">nested control</button></a>
  <ul><div>not a list item</div></ul>
</template>

良い

p はインラインの内容、table は tbody、ul は li を使います。独自コンポーネントの MyItem は、ul の子となる既知の HTML 要素としては検査されません。

<template>
  <p><span>inline in a paragraph</span></p>
  <table><tbody><tr><td>cell</td></tr></tbody></table>
  <ul><li>list item</li><MyItem /></ul>
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール

vue/use-unique-element-ids

静的 ID の代わりに useId() で一意な ID を生成します。

悪い例 · 良い例

既定の重大度: warning
プリセット: nuxt, opinionated
自動修正: なし。修正内容を確認してください
適用範囲: Vue SFC のテンプレートと各ブロック。ルールに必要な script の文脈も例に含めています。
オプション: ルール固有のオプションはありません。重大度とプリセットは設定できます。

設定(Vite+)

import { defineConfig } from "@vizejs/vite-plugin/vite-plus";

export default defineConfig({
  vue: {
    linter: {
      "preset": "incremental",
      "rules": {
        "vue/use-unique-element-ids": "warn"
      }
    },
  },
});
vp run lint

悪い

固定の email ID がコンポーネントの各インスタンスで重複し、複数表示時に label の参照先が曖昧になります。

<template>
  <label for="email">Email</label>
  <input id="email" />
</template>

良い

useId() の emailId を label の for と input の id の両方にバインドします。

<script setup>
import { useId } from "vue";

const emailId = useId();
</script>

<template>
  <label :for="emailId">Email</label>
  <input :id="emailId" />
</template>

良い例は上記の設定でこのルールの診断が出ない例です。他のルールの診断は出る場合があります。

実装 · 全ルール