コンテンツにスキップ
svelte-vitals
日本語
Esc
移動開く⌘Jプレビュー
このページの内容

architecture/unit-entry-file · ユニットのエントリファイル

ユニットとして宣言したディレクトリには、同名のファイルを置くべきです。

重大度: info · カテゴリ: architecture

チェック内容

「ユニット」として宣言したディレクトリに、同名のファイルが無い箇所を検出します。Card/Card.svelte が無い、getFoo/getFoo.ts が無い、といったケースです。

このルールは設定するまで無効です。何をユニットとみなすかについて既定の考えを持ちません。それは プロジェクトの規約であって、svelte-vitals が決めることではないからです。

なぜ重要か

ユニットの名前を持つのに本体ファイルが無いディレクトリは、未完成のユニットか、名前を間違えた グルーピングのどちらかです。いずれにせよツリーが意味を語らなくなり、規約に沿って解決しようとする 人間もツールも推測を始めることになります。

ファイル名のパターン照合ではこれを検出できません。パスを与えられれば「そのファイル名は親ディレクトリと 一致するか」を問えますが、存在しないファイルには照合すべきパスがありません。

修正方法

エントリファイルを追加するか、そのディレクトリをユニットの宣言から外します。実際にはグルーピングで あれば camelCase にリネームし、宣言が広すぎて巻き込んでいるなら宣言を狭めます。

設定

オプション デフォルト
units ディレクトリ glob → 拡張子 のマップ {}
pascalCaseUnits 起点 glob → 拡張子 のマップ {}
exclude ディレクトリ glob のリスト []

マップ形式の 2 つのオプション unitspascalCaseUnits では、値にエントリファイルの拡張子を 先頭のドットを含めて指定してください('ts' ではなく '.ts')。検証は空でない文字列なら通すため、ドットを忘れると設定の検証は通過したうえで誤動作します。 getFoo.ts ではなく getFoots を探して見つけられず、エントリファイルが実在するディレクトリに対して 「エントリファイルがない」と報告します。

exclude はマップではなくリストで、その値は拡張子ではなくディレクトリの glob です。

export default {
  rules: {
    'architecture/unit-entry-file': {
      options: {
        units: {
          'src/lib/api/**/*': '.ts',
          'src/**/functions/*': '.ts',
          'src/**/functions/*/*': '.ts',
          'src/**/stores/*': '.svelte.ts'
        },
        pascalCaseUnits: { 'src/**': '.svelte' },
        exclude: ['**/tests', '**/styleGuide', '**/types', '**/e2e']
      }
    }
  }
};

pascalCaseUnits の起点 glob は、その配下にある大文字始まりのディレクトリをすべて対象にします。 コンポーネントのユニットとは限らず、ルートセグメントや src/ 内にミラーされたアセットツリーも PascalCase でありえますが、どちらもユニットではありません。巻き込まれるままにせず、起点を狭めるか、 それらのツリーを exclude に加えてください。とりわけルートディレクトリでは注意が必要です。この ルールを満たすためにディレクトリ名を変更すると、サイトの URL が変わってしまいます。ルート セグメントについては、ディレクトリ名を変更するのではなく、宣言を狭めることで対処してください。

units はユニットを置き場所で識別します。pascalCaseUnits名前で識別し、マッチする 起点の配下にある、名前が大文字で始まるディレクトリすべてが対象になります。両方が必要な理由は、 camelCase のディレクトリはユニットでもグルーピングでもあり得るため位置でしか区別できず、一方で PascalCase のユニットは任意の深さに入れ子になるためパスの glob では見つけられないからです。

units にマッチしたディレクトリはその宣言に従い、pascalCaseUnits は残りにだけ適用されます。 1 つのディレクトリに複数の glob がマッチした場合は、次の順でより特異なものが優先されます。

  1. パスのセグメント数が多いほう
  2. ** セグメントが少ないほう
  3. キーが長いほう
  4. 辞書順で先のほう

セグメント数はワイルドカードも数えるため、実在のディレクトリ名を含むキーより、ワイルドカードだけで 深いキーのほうが勝つことがあります。意図した深さを書いてください。

exclude

exclude はそのディレクトリと配下すべてを対象外にします。 そのため、書けるのは「それ自体も ユニットでなく、配下にもユニットを持たない」ディレクトリだけです。

ディレクトリ exclude 可否
テスト・スタイルガイド・E2E・型のフォルダ
配下がユニットであるフォルダ 不可。中のユニットまで対象外になります

広い units glob がユニットを持つフォルダを巻き込んでいる場合は、除外するのではなく glob を 狭めてください。

glob の深さ

* は 1 つのパスセグメント内、** はセグメントをまたいでマッチしますが、2 つの形は対称では ありません。2 つのセグメントに挟まれた ** は 1 セグメント以上にマッチし、0 にはマッチしません。 そのため src/lib/api/**/*api/ の 2 段下以降を要求し、中間のグルーピング階層がユニットとして 扱われることを防ぎます。末尾/** は書いても安全です。「このディレクトリ配下すべて」を意味し、 ディレクトリ自身をユニットとして扱ってしまうことはありません。

制限

対象は src/ 配下のファイルだけです。その外のディレクトリは検査されないので、除外する必要も ありません。

1 つのディレクトリも検査していない units / pascalCaseUnits の宣言は報告されるので、glob の 書き間違いでルールが黙って何も検査しない状態にはなりません。「検査していない」は「パスにマッチ しなかった」より厳しい条件です。小文字始まりのディレクトリにしかマッチしなかった pascalCaseUnits のキーは、ユニットを 1 つも識別できていないため報告されます。書くつもりだった末尾の /** が抜けた キーは、これによって表面化します。

マッチしたディレクトリがすべて exclude で除外されている宣言も同じように報告され、その旨が メッセージに出ます(matched no directory ではなく matched only excluded directories)。手当てが 違うためです。前者は glob の書き間違いで、後者は目に見える 2 つのオプションどうしの矛盾です。

2 つのものは意図的に対象外です。

  • overrides エントリの中だけで宣言したキー。何にマッチしたかが、そのオーバーライドの適用 範囲に依存するためです。
  • exclude の glob。まったく検査しません。何にもマッチしない除外指定はレポートに影響しないため、 除外するつもりだったサブツリーがそもそも何も検出していなかった場合、書き間違えは気づかれない まま残ります。

1 つのディレクトリも検査していない宣言が複数ある場合、それらは 1 件ずつではなく、まとめて 1 件の finding として報告されます。そのため、この finding を抑制すると、何も検査していないすべての宣言に ついての検査が同時に抑制されます。

エントリファイルを欠いた宣言済みユニットが、同時に記法を誤ったディレクトリでもある場合、 architecture/directory-naming がその場所についても設定されていれば、そちらからも finding が 出ます。どちらもいずれかを抑制するわけではありません。それぞれ別の主張であり、両方とも真だからです。

モードによる違い

ありません。このルールが読むのは、ファイル内容ではなくプロジェクトのソースファイル一覧(src/** のパス)です。この一覧は CLI、Vite プラグインのビルド、ライブダッシュボードの静的ベースラインのいずれでも同じように作られるため結果は同一で、レンダリング済み HTML の解析で再評価されることもありません。--route で実行範囲を絞ると、このルールは動きません。一覧が作られず、ファイルの検出には紐づけるルートも無いためです。

無効化

個別に抑制するには、対象行の直前に <!-- svelte-vitals-disable-next-line architecture/unit-entry-file --> を置きます。ルールごと無効化するには、次のように設定します。

export default {
  rules: {
    'architecture/unit-entry-file': 'off'
  }
};