Skip to content

@tabula-css/stylelint-plugin

Tabula の CSS ガバナンス: 5 つのプロジェクト CSS ルール(@apply 禁止、手書きルール禁止、ルートのみのカスタムプロパティ、@theme inline 禁止、!important 禁止)を stylelint ルールとして提供し、さらに tabula check:css が実行する PostCSS カーネルも含みます。

インストール

bash
npm install --save-dev @tabula-css/stylelint-plugin

開発時依存です。postcss(^8.4.0)は必須のピア依存です。stylelint(^16.0.0)は任意のピア依存です — ./kernel エクスポートは stylelint への依存を一切持たず、これにより @tabula-css/clicheck:css は stylelint を CLI の依存グラフに引き込むことなくこれを再利用できます。

概要

このプロファイル内のすべての ESLint ルールは .ts/.tsx を統治しますが、このパッケージが存在するまでは、システム内の何もプロジェクトの .css ファイルを開くことはありませんでした。インポートされたスタイルシート内の手書きの .card .title { color: red } は、他のすべてのゲートが green のままでも局所性を破壊していました。@tabula-css/stylelint-plugin は、作成時にそのギャップを閉じます — 5 つのルールがエディタに stylelint 経由で配線され、そのルールカーネルtabula check:css と共有します。そのため、2 つの強制ポイントはまったく同じ PostCSS ロジックを読み取り、.css ファイルに何を含めてよいかについて食い違うことはありません。

有効化

js
// stylelint.config.js
import tabula from '@tabula-css/stylelint-plugin/config';

export default {
  ...tabula,
  overrides: [
    // The sanctioned entry — the one file holding `@import "../.tabula/source.css";` — is
    // exempt from `no-raw-rules` only, so it can also hold root-level application CSS.
    { files: ['src/app.css'], rules: { 'tabula/no-raw-rules': [true, { sanctionedEntry: true }] } },
  ],
};

@tabula-css/stylelint-plugin/config は、プロジェクトが自身の設定に展開する共有設定をエクスポートします: 5 つのルールすべてがオンで、重大度の緩和はありません — これらは、ESLint プラグインが .tsx で強制するのと同じ閉じた語彙の CSS 側の半分です。overrides のエントリリストは、tabula check:css --css-entry のフラグと同一に保ってください。この 2 つのゲートはルールロジックを共有しますが設定ファイルは共有しないため、許可されたエントリのリストこそが、両者の間でずれ得る唯一のものです。

ルール

以下の各ルールは、プロジェクト CSS — あなたやエージェントが書いたあらゆる .css ファイル — に対して発火し、特に断りのない限り .tabula/ 内の出力済みスタイルシートに対しても発火します(そこでの違反は、作成ミスではなくジェネレーターのバグです)。

tabula/no-apply

TAB-E221。どこであれ @apply を禁止します。これはクラスリストを手書きのセレクターへと合成するものであり、まさにプロファイルが取り除く間接参照です。

css
/* ❌ violating */
.card { @apply p-4 rounded-md; }

/* ✅ passing — write the classes on the element instead */

tabula/no-raw-rules

TAB-E222。許可されたエントリスタイルシート以外での手書きルールを禁止します。プロジェクト CSS のトップレベルには、@import@source@utility@custom-variant@charset、本体を持たない @layer a, b; の順序宣言、そしてコメントのみが許されます — それ以外(スタイルルール、@theme@media@layer { … } ブロック)は、クラスではなくセレクターによって到達するスタイリングです。トップレベルのブロックごとに 1 回、プロジェクトスコープでのみ報告されます。

css
/* ❌ violating */
.card p { color: red; }

/* ✅ passing */
@import "../.tabula/source.css";

(上記の overrides ブロック経由で){ sanctionedEntry: true } を渡すことで、@import "../.tabula/source.css"; の行を持つちょうど 1 つのファイルだけを、他のルールではなくこのルールから除外できます。

tabula/no-scoped-custom-property

TAB-E223このシステムにおける最も重要な単一のチェックです。 プロファイルのカスタムプロパティ(--tb-* / --d-*)を、ルート主体(:roothtml、あるいは属性セレクターで絞り込まれたそのいずれか — :root[data-theme="dark"] は該当しますが .card は該当しません)以外の場所で定義することを禁止します。プロジェクト CSS がプロファイルのプロパティをそもそも書く正当な理由はありません: dyn() が要素ごとの許可されたチャネルであり、それはインラインスタイルを書き込むのであって、スタイルシートを書き込むのではありません。

css
/* ❌ violating */
.panel { --tb-color-accent: red; }

/* ✅ passing */
:root[data-theme="dark"] { --tb-color-accent: #111; }

出力済みスコープでは、このルールは、実際にテーマトークンである(出力済みの集合のどこかで :root に定義されている)プロパティのみにフラグを立てます — プロファイル自身のユーティリティが要素ごとの合成プロパティ(.ring-accent { --tb-ring-color: … })を書き込むのは正当であり、それらは設計上 inherits: false かつ要素ごとです。

tabula/no-theme-inline

TAB-E224@theme inline を禁止します(空白で区切られたオプションとしてマッチするため、@theme inline reference@theme static inline の両方がこれを発火させます)。これはトークンの値をその使用箇所にインライン化してしまうため、ルートの軸ブロックはもはや別のテーマに向け直すことができなくなります — これは shadcn エコシステムの罠です。

css
/* ❌ violating */
@theme inline { --tb-color-accent: red; }

/* ✅ passing */
@theme { --tb-color-accent: red; }

tabula/no-important-css

TAB-E225。プロジェクト CSS 内のあらゆる宣言における !important を禁止します。これは、cn()/resolve() が依拠するランクモデルの外に宣言を置いてしまいます。出力済み CSS はジェネレーターの出力であるため、このルールは出力済みスコープでは実行されません。

css
/* ❌ violating */
.card { color: red !important; }

/* ✅ passing */
.card { color: red; }

@tabula-css/stylelint-plugin/kernel

ts
import {
  checkCss,
  CSS_RULE_CODES,
  collectRootDefinedProperties,
  isRootSubject,
  noApply,
  noImportantCss,
  noRawRules,
  noScopedCustomProperty,
  noThemeInline,
  type CheckCssOptions,
  type CssRuleName,
  type CssScope,
  type CssViolation,
} from '@tabula-css/stylelint-plugin/kernel';

上記のすべてのルールの背後にある純粋な PostCSS 実装で、インポートグラフに stylelint もファイルシステムも含みません — これはまさに @tabula-css/clicheck:css コマンドが直接インポートするものであり、CLI とエディタプラグインがバイト単位で同一のロジックを強制することを意味します。

ExportSignature (simplified)What it does
checkCss(root: Root, opts: CheckCssOptions) => CssViolation[]パースされたスタイルシートに対して 5 つのルールすべてを実行し、ソース順に違反を返します。
noApplynoRawRulesnoScopedCustomPropertynoThemeInlinenoImportantCss(root: Root, opts: CheckCssOptions) => CssViolation[]1 つのチェックだけを単独で必要とする呼び出し元向けに、各ルールを単体で。
collectRootDefinedProperties(root: Root) => Set<string>ルート主体で定義されているすべてのプロファイルカスタムプロパティ — no-scoped-custom-property が、出力済みスコープにおいてトークンと合成プロパティを区別するために必要とするテーマトークンの集合です。
isRootSubject(selector: string) => booleanセレクター文字列が、カンマで区切られたすべての分岐において、ドキュメントルートだけを選択するかどうか。
CSS_RULE_CODESReadonly<Record<CssRuleName, ErrorCode>>ルール名 → TAB-Exxx のマッピング。凍結されており、CLI とドキュメントがプラグインと食い違うことはありません。

CheckCssOptionsscope('project' | 'emitted'、デフォルト 'project')、メッセージ用の file ラベル、sanctionedEntry(no-raw-rules のみを除外)、そして — 出力済みスコープのみ — tokenProperties(collectRootDefinedProperties からの集合)を持ちます。

@tabula-css/stylelint-plugin/config

ts
import tabula, { config } from '@tabula-css/stylelint-plugin/config';

共有される stylelint 設定オブジェクトです: { plugins: ['@tabula-css/stylelint-plugin'], rules: { 'tabula/no-apply': true, 'tabula/no-raw-rules': true, 'tabula/no-scoped-custom-property': true, 'tabula/no-theme-inline': true, 'tabula/no-important-css': true } }有効化に示したとおり、あなた自身の stylelint.config.js に展開してください。

tabula check:css が実行すること

tabula check:css(および --no-css が渡されない限り tabula build --check)は、すべてのプロジェクト .css ファイルと .tabula/ 内の出力済みスタイルシートを歩き、このパッケージの checkCss カーネル関数をそれぞれに対して呼び出します — プロジェクトファイルには scope: 'project' を、出力済みファイルには 1 つの集合として scope: 'emitted'collectRootDefinedProperties からの tokenProperties を。フラグと終了時の挙動については @tabula-css/cli を参照してください。

関連パッケージ

  • @tabula-css/cli — このパッケージのカーネルを共有する CI 側のゲート。
  • Concepts — 5 つのルールそれぞれが存在する理由。
  • CSS governance — このゲートがまだカバーしていない範囲も含む全体像。
  • Getting started — 許可されたエントリスタイルシートの配線。

Released under the MIT License.