Redocly CLI lint ルールの実装
プレーンな言葉で書かれたチェック(「すべての配列スキーマは items を宣言しなければならない」)を、動作しテスト済みの Redocly lint ルールに変換します。
はしご。 Redocly ではチェックを実装する方法が3つあります。最初の方法が最もメンテナンスコストが低くなっています。
- ビルトインルール — ビルトインルール のいずれかがチェックを行います。
- 設定可能ルール —
redocly.yaml内の1つ以上のrule/*エントリがアサーションを使用してチェックを行います。 - カスタムプラグイン — ビジターパターンで独自のルールを実装する JavaScript コードです。この段は最後に使用します。
チェックは1つずつ行ってください。 各チェックを段1から開始します。 現在の段でチェックができない場合のみ、1つ下の段に進みます。 すべての構文は公式ドキュメントまたはクックブックから取得します。 記憶ではなくドキュメントを信頼してください。 CommonJS ではなく ESM など、モダンな構文を使用します。プロジェクトに独自のスタイルがある場合は、それに従います。
1. トリアージ
各チェックについて、3つのことを書き留めます。それが調べるノード、真でなければならないこと、違反がどのように見えるか。 各チェックを個別にトリアージします。異なるチェックが異なる段に該当する可能性があります。
ノード名は inspect-node-types コマンドから取得し、記憶からは取得しません。
タイプツリーは仕様とバージョンごとに異なります。
コマンドが出力する名前は、設定可能ルールの subject.type と、プラグインのビジターキーです。
| 学習内容 | 実行コマンド |
|---|---|
| 1つの場所のタイプ | --pointer='#/paths/~1orders' |
| すべてのノード、または1つのタイプのすべてのノード | フラグなし、または --type=Schema |
| 説明が使用するタイプ | --summary |
| タイプが配置可能な場所 | --type=<Type> --parents |
| 1つのノードの祖先 | --pointer=<pointer> --parents |
最後の2行はルールにスコープを与えます。
タイプが複数の親の下に配置される場合、ルールには where ゲートが必要です。
そのゲートがないと、ルールは望まないブランチも報告します。
このコマンドは目の前の説明を表示します。仕様が許可するすべてを表示するわけではありません。
コマンドがブランチを出力しない場合、そのブランチは未検証です。存在しないわけではありません。
このコマンドはステップ3を短縮しません。タイプ名を提供します。アサーションがチェックを表現しているかどうかは教えてくれません。
各チェックについてはしごを歩んでください:
- ビルトインルールリストで概念を検索します。
パスの末尾のスラッシュは
no-path-trailing-slashを与えます。 設定が既にビルトインを有効にするルールセットを拡張している場合は、ユーザーにその旨を伝えます。ルールを再度追加しないでください。 ビルトインの説明がチェックと一致する場合、それが置き換えるルールよりも多く報告する場合でも、ビルトインを使用してください。 ビルトインは通常、同じアイデアのより良いバージョンであるため、その追加の検出結果はチェックが正しく行われたものです。 より狭い動作を手動で記述すると、メンテナンスされたルールを、自分でメンテナンスしなければならないコードと交換することになります。 ビルトインが異なるものをチェックする場合のみ、1つ下の段に進みます。 どのビルトインを採用したか、そしてそれが何を追加するかを報告します。 - ビルトインがプロジェクトがリントする仕様バージョンをサポートしていることを確認します。
一部のビルトインは1つのバージョンのみをサポートします。
no-invalid-media-type-examplesは OpenAPI 3.x をサポートするため、Swagger 2.0 のチェックは適用されないままになります。 有効にしたルールがドキュメントに適用できない場合、Redocly はredocly.yaml で未使用のルールが見つかりましたと出力します。 その行を修正します。ノイズではありません。 - アサーションと
whereゲートを試します。後述の「設定可能ルール」を参照してください。 1つのチェックに複数のrule/*エントリが必要な場合があり、それぞれがケースまたは場所ごとに異なります。それでもプラグインよりは優れています。 - 設定可能ルールがチェックを表現できない場合(組み合わせても)にのみ、プラグインを記述します。 最初に以下の3つのダイヤルを確認します。 プラグインは任意のチェックを実行できますが、コードです。複雑さ、メンテナンス、そして独自のバグを追加します。
- 設定可能ルールで実際のギャップを埋められない場合のみ、ユーザーに問題を報告するよう伝えます。
各チェックに段が割り当てられたらトリアージは完了です。 各チェックに1つの解決策を提供します。それは機能する最も高い段です。 ユーザーが行動を起こさなければならないものだけを報告します。説明と実装の不一致、未検証のルール、または別の方法に進む可能性のある決定。 チャットで報告します。サマリーファイルを決して書きません。 設定が残りを記録するため、各チェックの段について説明する必要はありません。
他のリンターから移行する
- 実際に有効なルールセットを移行します。ファイルに書かれているルールだけではありません。
extends、または継承されたベースは、デフォルトの重大度でさらに多くのルールを有効にします。それらの各ルールもチェックです。 そのベースを解決し、開始する前にルールを数えます。そうすれば、作業の真の規模がわかります。 - 各チェックを2つの場所から取得します。ルールの説明とその実装です。 ルール名の記憶から決して取得しないでください。 両者が一致しない場合は、説明を使用します。説明は作者が望んでいたことを示し、実装にはバグが含まれている可能性があります。 その違いについてユーザーに伝えます。
- 重大度が
offのルールはスキップします。無効なエントリとしても移行しません。 有効なルールが完了したら、スキップした各ルールを1行でリストします。その後、ユーザーが決定できます。 - Spectral の場合は、まず移行ガイドのマップテーブルを読みます。 多くの Spectral コアルールは1つのビルトインにマップされます。空のセルは、はしごを下りることを意味します。
- ソースから関数をコピーする場合、そのバグもより厳格なホストにコピーすることになります。
Redocly はルールがスローされるとドキュメント全体を停止しますが、ソースリンターは1つのルールのみを停止できます。
ガードされていないマッチ、例えばマッチに失敗する可能性のある値に対する
v.match(/…/)[0]は、そのファイル内のすべての検出結果を削除します。 コピーするコードにガードを追加します。その後、その難しい形状を持つドキュメントをリントします。
2. 設定
ユーザーの redocly.yaml に書き込みます。この正確な名前を使用し、先頭にドットは付けません。
ファイルが存在する場合は、最初に読み取り、既存の extends とルールを保持します。
各ルールを error に設定します。ただし、ユーザーが別のレベルを要求した場合はその限りではありません。
移行する場合は、ソースの重大度を保持し、最も近い Redocly レベルを選択します。
error(実行を失敗させる)、warn、off のみが存在するため、info と hint は warn になります。
設定から作成するすべてのファイルを参照し、リント実行でそれを実行します。デッドファイルを残さないでください。
extends がない設定は何も継承しません — recommended も struct も継承しません。
デフォルトベースは、設定ファイルが存在しない場合にのみ適用されます。
したがって、redocly.yaml は名前を付けたルールのみを正確に実行します。
ビルトインの段は、extends がルールセットを指定している場合、または設定自体が各ビルトインをリストしている場合にのみ存在します。
どちらもない場合、仕様を3回破るドキュメントでもリントはクリーンになります。
すべてのルールを配線します。
plugins: 内のプラグインは、そのルールが rules: に存在しない場合は何も行わず、Redocly は警告を出しません。
設定を記述した後、アクティブでなければならないルール ID をリストします。
各 ID が rules: または spec スコープのブロックにあることを確認します。
すべてのルールが配置されたら、リストを事前定義されたルールセットと比較します:minimal、recommended、recommended-strict、spec。
- ルールが1つのルールセットに近い場合は、それを拡張します。 差分のみをオーバーライドとして保持します。他の重大度、オフにするルール、ルールセットが保持していないチェック。
- ルールセットは、ユーザーが要求しなかったルールも有効にします。
それが追加するものを読み、記述したルールと矛盾するか繰り返す各ルールをオーバーライドします。
ソースが
offに設定しているチェックを有効にするルールセットは、利点ではなく修正すべきエラーです。 - オーバーライドがルールと同じ数かそれ以上の場合は、プレーンなリストを保持し、
extendsは使用しません。
3. 検証
ルールは、ライブ実行で報告されるまで未検証です。
npx redocly lint <api-file> で各ルールを2つの方向で証明します:
- 違反フィクスチャ:ルールが正しい場所で報告されること。
- 準拠フィクスチャ:実行がサイレントのままであること。
偽陽性は一般的な失敗であるため、違反フィクスチャのみを通過したルールは未検証です。 違反フィクスチャを作成して、このルールを隣のルール(置き換えるルール、または設定内の隣のルール)から分離します。 2つのエラーはその分離を必要とします:
- 違反フィクスチャに falsy な値を入れます。
空の
descriptionを拒否しなければならないルールは、description: ''で報告しなければなりません。 準拠フィクスチャにのみ''を入れた場合、存在アサーションがそれを受け入れるため、何も証明できません。 ルールは正しく見え、ギャップは残ります。 - 2つのソースチェックを置き換えるビルトインは、2番目のチェックのみを破るフィクスチャを必要とします。
no-unsafe-markdownは<script>を報告しますが、eval(は報告しません。 両方を含むフィクスチャは、ビルトインが行わないチェックに対するクレジットを与えます。
出力をクリーンに保つために、--config で小さな一時設定を渡します。
完了したら、その設定とフィクスチャを削除します。
提供するすべてのスニペットは、書かれたまま正しくなければなりません。YAML としてパースされ、主張するように動作します。
各チェックが報告された違反フィクスチャとサイレントだった準拠フィクスチャを指定したら完了です。
レポートでそれらのフィクスチャを指定します。
数は証拠ではありません。
プロジェクト自身のファイルでのクリーンな実行も証拠ではありません。そこで何も報告されないことを示すだけで、それは正しいルールとデッドルールの両方に当てはまります。
リスクのあるルールを検証し、他のルールを仮定した場合、成果物は未検証です。その旨を述べ、各未検証ルールを指定します。
準拠フィクスチャは、その内部の形状のみを証明します。
試していない仕様バージョン、または --parents が表示したが実行しなかった場所は、未テストのままです。それを報告します。
ライブ実行が不可能な場合、ルールを未検証とマークします。動作すると主張しないでください。
また、要求された各チェックが設定またはスキップリストにあり、プロジェクトが作成した設定ファイルとプラグインファイルのみを保持している場合も完了です。
設定可能ルール
ドキュメント:設定可能ルール。 動作例:cookbook/configurable-rules。
ルールは、rules: の下で rule/ で始まる名前のエントリです:
rules:
rule/my-check:
subject:
type: Schema # ドキュメントのタイプリストからのノードタイプ
property: title # オプション
assertions:
defined: true
where: [] # オプションのゲート
message: カスタム問題テキスト。
severity: error
プレーンな rules は、プロジェクトがリントするすべてのドキュメントに適用されます。デフォルトではルールをそこに保持します。
チェックが特定の仕様バージョンにのみ存在するもの(OAS 3.0 の nullable など)を必要とする場合、またはユーザーがスコーピングを要求した場合のみ、ルールを spec スコープのキーの下に移動します。
キーは oas2Rules、oas3_0Rules、oas3_1Rules、oas3_2Rules、async2Rules、async3Rules、arazzo1Rules、overlay1Rules です。
1つのキー内のルールは他のバージョンでは実行されないため、プロジェクトが2つのバージョンで必要とするチェックは、両方のバージョンに到達するエントリが必要です。
代わりに、プラグイン内の rules マップでプラグインルールをスコープします。
アサーションがテストするもの
2つの事実が、ほとんどの偽陽性とほとんどのサイレントミスを引き起こします。
値が存在しない場合、アサーションはパスします。
ほとんどすべてのアサーションは、チェックする値が存在しない場合に成立します。
property: type に対する const: array は、型が array のスキーマにマッチし、type がないすべてのスキーマにもマッチします。
したがって、where エントリがプロパティ値をテストする場合、そのプロパティが存在しない可能性がある場合は、値アサーションの横に defined: true を追加します。
存在は値ではありません。
defined、required、requireAny、disallowed、mutuallyExclusive は、キーが存在するかどうかをテストします。
内容については何も言わないため、description に対する defined: true は description: '' でもパスします。
代わりに値をアサーションします。nonEmpty: true は空文字列を拒否し、const、enum、pattern は内容をテストします。
これは実際に最も頻繁に見られる欠陥です。
レビューでは正しく見え、'' を含む違反フィクスチャだけがそれを示します。
これらの事実の2つの結果:
const: <値>は「このフィールドは falsy でなければならない」と言う方法でもあります。アサーションはその値と存在しないフィールドの両方で成立するためです。requireAnyはキーのみをカウントします。 「これらのフィールドの少なくとも1つ、かつ空ではない」にはアサーションフォームがありません。そのチェックは真のプラグインケースです。
アサーションが検査するもの
property: name— サブジェクトノード上のそのフィールドの値。property: [name, url, email]— 各リストされたフィールドを個別に。 失敗する各フィールドは、独自の JSON ポインタ(例:#/info/contact/name)を持つ独自の問題になります。propertyがない場合 — サブジェクトノードのキー。 例えば、subject: { type: Paths }とassertions: { notPattern: /.+\/$/ }は、パス文字列自体に対して実行されます。
サブジェクトを狙い、ゲートで制限する
3つのダイヤルがルールを狙います。この順序で解決します。
1. サブジェクト:アサーションするものを所有するノード。
- マップが保持するキーに関するチェックは、マップタイプ自体をターゲットにします。
レスポンス、ヘッダー、スキーマプロパティ、パスのマップは、それぞれ独自のタイプです。
したがって、「このマップはそのキーを保持しなければならない」は、1つのサブジェクトに対する1つの
requiredです。親を検索しないでください。 - 値に関するチェックは、フィールドを保持するノードに
propertyを加えたものをターゲットにします。 type: anyとpropertyを組み合わせると、そのプロパティが現れるすべての場所でチェックします。inspect-node-typesでタイプ名を確認します。ルールがターゲットとする仕様バージョンのドキュメントに対して実行します。 名前付きタイプは、あるバージョンに存在し、別のバージョンには存在しない場合があります。HeadersMapは OpenAPI 3 のタイプです。OpenAPI 2 では、レスポンスのheadersマップは匿名であり、タイプ名はないため、oas2Rulesの下のHeadersMapサブジェクトは決して報告しません。 Redocly は未解決のルール ID については警告しますが、未解決のsubject.typeについては警告しません。そのため、間違ったタイプは決して報告しないルールになります。
…
[翻訳のために切り詰められました]