@markuplint/ml-config 4.8.13 → 4.8.15
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.ja.md +282 -0
- package/ARCHITECTURE.md +282 -0
- package/CHANGELOG.md +5 -1
- package/SKILL.md +114 -0
- package/docs/maintenance.ja.md +180 -0
- package/docs/maintenance.md +180 -0
- package/lib/merge-config.d.ts +24 -0
- package/lib/merge-config.js +24 -0
- package/lib/types.d.ts +112 -0
- package/lib/utils.d.ts +33 -6
- package/lib/utils.js +33 -6
- package/package.json +6 -6
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# @markuplint/ml-config
|
|
2
|
+
|
|
3
|
+
## 概要
|
|
4
|
+
|
|
5
|
+
`@markuplint/ml-config` は markuplint の設定システムの中核パッケージです。`Config` 型階層、複数の設定レイヤー(ベース、extends、overrides)を一つの最適化された設定にマージするアルゴリズム、キャプチャ変数をルール設定に注入する Mustache テンプレートレンダリングシステムを提供します。`@markuplint/file-resolver`(設定ファイルの読み込みと解決)と `@markuplint/ml-core`(マージ済み設定を使用してルールを適用)の間に位置します。
|
|
6
|
+
|
|
7
|
+
## ディレクトリ構成
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── index.ts — すべての公開 API を再エクスポート
|
|
12
|
+
├── types.ts — 全型定義(Config, Rule, Pretender, Violation 等)
|
|
13
|
+
├── merge-config.ts — マージアルゴリズム(mergeConfig, mergeRule, ヘルパー関数)
|
|
14
|
+
├── merge-config.spec.ts — マージアルゴリズムのテスト
|
|
15
|
+
├── utils.ts — テンプレートレンダリング、ルール正規化、型ガード
|
|
16
|
+
└── utils.spec.ts — ユーティリティのテスト
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## 型システム
|
|
20
|
+
|
|
21
|
+
### Config 型階層
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
classDiagram
|
|
25
|
+
class Config {
|
|
26
|
+
+$schema?: string
|
|
27
|
+
+extends?: string | string[]
|
|
28
|
+
+plugins?: (PluginConfig | string)[]
|
|
29
|
+
+parser?: ParserConfig
|
|
30
|
+
+parserOptions?: ParserOptions
|
|
31
|
+
+specs?: SpecConfig
|
|
32
|
+
+excludeFiles?: string[]
|
|
33
|
+
+severity?: SeverityOptions
|
|
34
|
+
+pretenders?: Pretender[] | PretenderDetails
|
|
35
|
+
+rules?: Rules
|
|
36
|
+
+nodeRules?: NodeRule[]
|
|
37
|
+
+childNodeRules?: ChildNodeRule[]
|
|
38
|
+
+overrideMode?: "merge" | "reset"
|
|
39
|
+
+overrides?: Record~string, OverrideConfig~
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class OverrideConfig {
|
|
43
|
+
<<Omit Config NoInherit>>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class OptimizedConfig {
|
|
47
|
+
+plugins?: PluginConfig[]
|
|
48
|
+
+pretenders?: PretenderDetails
|
|
49
|
+
+overrides?: Record~string, OptimizedOverrideConfig~
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Config --> OverrideConfig : "Omit $schema, extends,\noverrideMode, overrides"
|
|
53
|
+
Config --> OptimizedConfig : "mergeConfig()"
|
|
54
|
+
OverrideConfig --> OptimizedOverrideConfig : "mergeConfig()"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Config から OptimizedConfig への変換
|
|
58
|
+
|
|
59
|
+
| フィールド | Config | OptimizedConfig | 変換 |
|
|
60
|
+
| ------------ | --------------------------------- | ----------------------------------------- | -------------------------------------- |
|
|
61
|
+
| `plugins` | `(PluginConfig \| string)[]` | `PluginConfig[]` | 文字列を `{name}` オブジェクトに正規化 |
|
|
62
|
+
| `pretenders` | `Pretender[] \| PretenderDetails` | `PretenderDetails` | 配列を `{data: [...]}` に変換 |
|
|
63
|
+
| `extends` | `string \| string[]` | 削除 | マージ後は不要 |
|
|
64
|
+
| `$schema` | `string` | 削除 | メタデータのみ |
|
|
65
|
+
| `overrides` | `Record<string, OverrideConfig>` | `Record<string, OptimizedOverrideConfig>` | 各値を再帰的にマージ |
|
|
66
|
+
|
|
67
|
+
### ルール型の3形式
|
|
68
|
+
|
|
69
|
+
ルールは3つの形式で設定できます:
|
|
70
|
+
|
|
71
|
+
| 形式 | 型 | 例 | 意味 |
|
|
72
|
+
| ------- | ----------------- | ------------------------------------ | --------------------------- |
|
|
73
|
+
| Boolean | `boolean` | `true` / `false` | デフォルトで有効化 / 無効化 |
|
|
74
|
+
| Value | `RuleConfigValue` | `"always"`, `["a","b"]`, `null` | ショートハンド値 |
|
|
75
|
+
| Object | `RuleConfig<T,O>` | `{severity, value, options, reason}` | フル設定 |
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type Rule<T, O> = RuleConfig<T, O> | Readonly<T> | boolean;
|
|
79
|
+
|
|
80
|
+
type RuleConfig<T, O> = {
|
|
81
|
+
severity?: Severity; // 'error' | 'warning' | 'info'
|
|
82
|
+
value?: Readonly<T>;
|
|
83
|
+
options?: Readonly<O>;
|
|
84
|
+
reason?: string;
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### NodeRule / ChildNodeRule
|
|
89
|
+
|
|
90
|
+
- `NodeRule` -- CSS セレクタ、正規表現セレクタ、ARIA ロール、カテゴリ、obsolete フラグで対象ノードを限定し、ルール設定をオーバーライド
|
|
91
|
+
- `ChildNodeRule` -- `NodeRule` と類似だが子ノードを対象とする。`inheritance` フラグで子孫への継承を制御
|
|
92
|
+
|
|
93
|
+
### Pretender 型
|
|
94
|
+
|
|
95
|
+
- `Pretender` -- CSS セレクタを使用してカスタム要素を標準要素に見せかける。`as` フィールドで要素名または詳細な `OriginalNode` を指定
|
|
96
|
+
- `OriginalNode` -- 要素名、スロット、名前空間、属性、継承属性、ARIA プロパティを定義
|
|
97
|
+
- `PretenderDetails` -- マージ後に使用される正規化形式 `{data?, files?, imports?}`
|
|
98
|
+
|
|
99
|
+
## マージアルゴリズム
|
|
100
|
+
|
|
101
|
+
このパッケージの中核です。`mergeConfig()` 関数はプロパティごとの戦略で2つの設定を統合します。
|
|
102
|
+
|
|
103
|
+
### mergeConfig() の全体フロー
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
mergeConfig(a: Config, b?: Config): OptimizedConfig
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```mermaid
|
|
110
|
+
flowchart TD
|
|
111
|
+
A["入力: ベース (a) + オーバーライド (b)"] --> B{"b が指定されている?"}
|
|
112
|
+
B -->|Yes| C["deleteExtendsProp = true"]
|
|
113
|
+
B -->|No| C2["b = {}, deleteExtendsProp = false"]
|
|
114
|
+
C --> D["スプレッド: {...a, ...b}\n(プリミティブフィールドは右辺優先)"]
|
|
115
|
+
C2 --> D
|
|
116
|
+
D --> E["プロパティ別マージ戦略を適用\n(下記テーブル参照)"]
|
|
117
|
+
E --> F{"deleteExtendsProp?"}
|
|
118
|
+
F -->|Yes| G["結果から extends を削除"]
|
|
119
|
+
F -->|No| H["extends を保持"]
|
|
120
|
+
G --> I["deleteUndefProp()\nundefined プロパティを除去"]
|
|
121
|
+
H --> I
|
|
122
|
+
I --> J["OptimizedConfig を返却"]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### プロパティ別マージ戦略テーブル
|
|
126
|
+
|
|
127
|
+
| プロパティ | 戦略 | ヘルパー関数 | 詳細 |
|
|
128
|
+
| ---------------- | ----------------------- | ---------------------------------------------------- | --------------------------------------------- |
|
|
129
|
+
| `plugins` | 結合+重複排除+正規化 | `concatArray(uniquely=true, comparePropName='name')` | 同名プラグインは settings を deep merge |
|
|
130
|
+
| `parser` | オブジェクト deep merge | `mergeObject()` | 右辺優先、deepmerge ライブラリ使用 |
|
|
131
|
+
| `parserOptions` | オブジェクト deep merge | `mergeObject()` | 同上 |
|
|
132
|
+
| `specs` | オブジェクト deep merge | `mergeObject()` | 同上 |
|
|
133
|
+
| `excludeFiles` | 結合+重複排除 | `concatArray(uniquely=true)` | 単純な値の重複排除 |
|
|
134
|
+
| `severity` | オブジェクト deep merge | `mergeObject()` | parser と同様 |
|
|
135
|
+
| `pretenders` | 形式変換+deep merge | `mergePretenders()` | 配列を PretenderDetails に変換後にマージ |
|
|
136
|
+
| `rules` | ルール別マージ | `mergeRules()` → `mergeRule()` | **最も複雑 -- 次節で詳述** |
|
|
137
|
+
| `nodeRules` | 結合(重複排除なし) | `concatArray()` | 両配列を単純連結 |
|
|
138
|
+
| `childNodeRules` | 結合(重複排除なし) | `concatArray()` | nodeRules と同様 |
|
|
139
|
+
| `overrideMode` | 右辺優先 | `b.overrideMode ?? a.overrideMode` | 単純な優先順位 |
|
|
140
|
+
| `overrides` | キー別再帰マージ | `mergeOverrides()` | 各キーに対して `mergeConfig()` を再帰呼び出し |
|
|
141
|
+
| `extends` | 結合→削除 | `concatArray()` | マージ後に結果から削除 |
|
|
142
|
+
|
|
143
|
+
### mergeRule() -- ルールマージの詳細
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
mergeRule(a: Nullable<AnyRule>, b: AnyRule): AnyRule
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
最も複雑なマージロジックを処理する関数です。両方の入力はまず `optimizeRule()` で正規化されます(非推奨の `option` から `options` への移行を含む)。
|
|
150
|
+
|
|
151
|
+
```mermaid
|
|
152
|
+
flowchart TD
|
|
153
|
+
Start["mergeRule(a, b)"] --> OptAB["optimizeRule() で両方を正規化"]
|
|
154
|
+
OptAB --> ChkFalse{"b === false OR\nb.value === false?"}
|
|
155
|
+
ChkFalse -->|Yes| RetFalse["return false\n(絶対無効化)"]
|
|
156
|
+
ChkFalse -->|No| ChkAUndef{"a === undefined?"}
|
|
157
|
+
ChkAUndef -->|Yes| RetB["return b"]
|
|
158
|
+
ChkAUndef -->|No| ChkBUndef{"b === undefined?"}
|
|
159
|
+
ChkBUndef -->|Yes| RetA["return a"]
|
|
160
|
+
ChkBUndef -->|No| ChkBVal{"b は Value?\n(primitive/null/array)"}
|
|
161
|
+
ChkBVal -->|Yes| ChkAVal{"a は Value?"}
|
|
162
|
+
ChkAVal -->|Yes| ChkBothArr{"両方とも配列?"}
|
|
163
|
+
ChkBothArr -->|Yes| RetConcat["return [...a, ...b]\n(連結)"]
|
|
164
|
+
ChkBothArr -->|No| RetBVal["return b\n(右辺優先)"]
|
|
165
|
+
ChkAVal -->|No| MergeValObj["a の severity/reason を保持\nvalue を置換\n(配列は連結)"]
|
|
166
|
+
ChkBVal -->|No| MergeObj["severity: b ?? a\nvalue: b ?? a\noptions: mergeObject(a, b)\nreason: b ?? a"]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**重要な設計判断:**
|
|
170
|
+
|
|
171
|
+
1. **`false` は絶対無効化** -- override が `false`(または `{value: false}`)なら、base が何であっても結果は常に `false`
|
|
172
|
+
2. **配列値は連結** -- `["a","b"]` + `["c","d"]` は `["a","b","c","d"]` になり、extends チェーンでルールを段階的に追加可能
|
|
173
|
+
3. **options は deep merge** -- severity、value、reason は右辺優先だが、options のみ `mergeObject()`(deepmerge ライブラリによる deep merge)を使用
|
|
174
|
+
|
|
175
|
+
### ヘルパー関数
|
|
176
|
+
|
|
177
|
+
#### concatArray(a, b, uniquely?, comparePropName?)
|
|
178
|
+
|
|
179
|
+
オプションの重複排除付きで2つの配列を連結:
|
|
180
|
+
|
|
181
|
+
- `uniquely=false` -- 単純な連結、重複排除なし
|
|
182
|
+
- `uniquely=true`、`comparePropName` なし -- 完全一致で重複排除
|
|
183
|
+
- `uniquely=true`、`comparePropName` あり -- 指定プロパティ名で重複排除。同名オブジェクトは `mergeObject()` でマージ(例: プラグインの settings)
|
|
184
|
+
- 空の結果には `undefined` を返す
|
|
185
|
+
|
|
186
|
+
#### mergeObject(a, b)
|
|
187
|
+
|
|
188
|
+
`deepmerge` ライブラリを使用した再帰的な deep merge。右辺の値が優先。結果から undefined プロパティを除去。
|
|
189
|
+
|
|
190
|
+
#### mergeOverrides(a, b)
|
|
191
|
+
|
|
192
|
+
両方の override レコードから全キーの集合を取得。各キーに対して `mergeConfig(a[key], b[key])` を再帰呼び出し。各結果から `$schema`、`extends`、`overrides` を削除(これらはトップレベル専用プロパティ)。
|
|
193
|
+
|
|
194
|
+
#### mergePretenders(a, b)
|
|
195
|
+
|
|
196
|
+
配列形式の pretenders を正規化形式 `PretenderDetails`(`{data: [...]}`)に変換してから `mergeObject()` で deep merge。
|
|
197
|
+
|
|
198
|
+
## テンプレートレンダリングシステム
|
|
199
|
+
|
|
200
|
+
### provideValue(template, data)
|
|
201
|
+
|
|
202
|
+
Mustache テンプレート文字列を提供されたデータでレンダリング:
|
|
203
|
+
|
|
204
|
+
- テンプレートに変数がない -- テンプレートをそのまま返す
|
|
205
|
+
- 変数があるが data にマッチするキーがない -- `undefined` を返す
|
|
206
|
+
- 変数があり data にマッチするキーがある -- レンダリング結果を返す
|
|
207
|
+
|
|
208
|
+
### exchangeValueOnRule(rule, data)
|
|
209
|
+
|
|
210
|
+
ルール設定内のすべての文字列値に Mustache テンプレートレンダリングを適用:
|
|
211
|
+
|
|
212
|
+
- **value** -- 文字列値はレンダリングされる。配列の各要素も個別にレンダリング
|
|
213
|
+
- **options** -- options オブジェクト内のすべての文字列値を再帰的にレンダリング
|
|
214
|
+
- **reason** -- 文字列としてレンダリング
|
|
215
|
+
|
|
216
|
+
この関数は `nodeRules` / `childNodeRules` の `regexSelector` で使用され、キャプチャグループ(`$0`、`$1`、`dataName` 等の名前付きキャプチャ)がテンプレート変数としてルール設定に注入されます。
|
|
217
|
+
|
|
218
|
+
## ユーティリティ関数
|
|
219
|
+
|
|
220
|
+
| 関数 | 目的 |
|
|
221
|
+
| --------------------- | -------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `cleanOptions()` | 非推奨の `option` フィールドを `options` に正規化し、標準フィールドを抽出して undefined プロパティを除去 |
|
|
223
|
+
| `isRuleConfigValue()` | 型ガード: プリミティブ、`null`、配列(= `RuleConfig` オブジェクトではない)に対して `true` を返す |
|
|
224
|
+
| `deleteUndefProp()` | プレーンオブジェクトから `undefined` 値のプロパティをすべて in-place で削除 |
|
|
225
|
+
|
|
226
|
+
## 主要ソースファイル
|
|
227
|
+
|
|
228
|
+
| ファイル | 目的 |
|
|
229
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
230
|
+
| `src/types.ts` | 全型定義(Config, Rule, Pretender, Violation 等) |
|
|
231
|
+
| `src/merge-config.ts` | `mergeConfig()`、`mergeRule()`、全ヘルパー関数 |
|
|
232
|
+
| `src/utils.ts` | `provideValue()`、`exchangeValueOnRule()`、`cleanOptions()`、`isRuleConfigValue()`、`deleteUndefProp()` |
|
|
233
|
+
| `src/index.ts` | 全公開 API の再エクスポート |
|
|
234
|
+
|
|
235
|
+
## 外部依存関係
|
|
236
|
+
|
|
237
|
+
| 依存パッケージ | 用途 |
|
|
238
|
+
| ---------------------- | --------------------------------------------------- |
|
|
239
|
+
| `@markuplint/ml-ast` | `ParserOptions` 型(型のみ) |
|
|
240
|
+
| `@markuplint/selector` | `RegexSelector` 型(再エクスポート) |
|
|
241
|
+
| `@markuplint/shared` | `Nullable` ユーティリティ型 |
|
|
242
|
+
| `deepmerge` | `mergeObject()` の deep merge 実装 |
|
|
243
|
+
| `is-plain-object` | `deleteUndefProp()` でのプレーンオブジェクト判定 |
|
|
244
|
+
| `mustache` | `provideValue()` のテンプレートレンダリングエンジン |
|
|
245
|
+
| `type-fest` | `Writable` ユーティリティ型 |
|
|
246
|
+
|
|
247
|
+
## 統合ポイント
|
|
248
|
+
|
|
249
|
+
```mermaid
|
|
250
|
+
flowchart LR
|
|
251
|
+
subgraph upstream ["上流"]
|
|
252
|
+
fileResolver["@markuplint/file-resolver\n(設定ファイル読み込み,\nextends チェーン解決)"]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
subgraph pkg ["@markuplint/ml-config"]
|
|
256
|
+
mergeConfig["mergeConfig()\n(マージアルゴリズム)"]
|
|
257
|
+
types["Config 型定義"]
|
|
258
|
+
templates["テンプレートレンダリング"]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
subgraph downstream ["下流"]
|
|
262
|
+
mlCore["@markuplint/ml-core\n(OptimizedConfig を使用して\nルールを適用)"]
|
|
263
|
+
rules["@markuplint/rules\n(Rule<T,O>,\nRuleConfig<T,O> 型を使用)"]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
fileResolver -->|"各 extends レイヤーで\nmergeConfig() を呼び出し"| mergeConfig
|
|
267
|
+
mergeConfig -->|"OptimizedConfig を生成"| mlCore
|
|
268
|
+
types -->|"Rule, RuleConfig 型"| rules
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### 上流
|
|
272
|
+
|
|
273
|
+
- **`@markuplint/file-resolver`** -- 設定ファイルを読み込み、extends チェーンを解決し、`mergeConfig()` を呼び出してレイヤーを統合
|
|
274
|
+
|
|
275
|
+
### 下流
|
|
276
|
+
|
|
277
|
+
- **`@markuplint/ml-core`** -- マージ済みの `OptimizedConfig` を受け取り、パース済みドキュメントにルールを適用
|
|
278
|
+
- **`@markuplint/rules`** -- `Rule<T,O>` と `RuleConfig<T,O>` 型を使用してルール実装を定義
|
|
279
|
+
|
|
280
|
+
## ドキュメントマップ
|
|
281
|
+
|
|
282
|
+
- [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、レシピ、トラブルシューティング
|
package/ARCHITECTURE.md
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
# @markuplint/ml-config
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
`@markuplint/ml-config` is the configuration system core for markuplint. It provides the `Config` type hierarchy, the merge algorithm that combines multiple configuration layers (base, extends, overrides) into a single optimized config, and a Mustache template rendering system for injecting captured variables into rule settings. The package sits between `@markuplint/file-resolver` (which reads and resolves config files) and `@markuplint/ml-core` (which applies rules using the merged config).
|
|
6
|
+
|
|
7
|
+
## Directory Structure
|
|
8
|
+
|
|
9
|
+
```
|
|
10
|
+
src/
|
|
11
|
+
├── index.ts — Re-exports all public APIs
|
|
12
|
+
├── types.ts — All type definitions (Config, Rule, Pretender, Violation, etc.)
|
|
13
|
+
├── merge-config.ts — Merge algorithm (mergeConfig, mergeRule, helpers)
|
|
14
|
+
├── merge-config.spec.ts — Merge algorithm tests
|
|
15
|
+
├── utils.ts — Template rendering, rule normalization, type guards
|
|
16
|
+
└── utils.spec.ts — Utils tests
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Type System
|
|
20
|
+
|
|
21
|
+
### Config Type Hierarchy
|
|
22
|
+
|
|
23
|
+
```mermaid
|
|
24
|
+
classDiagram
|
|
25
|
+
class Config {
|
|
26
|
+
+$schema?: string
|
|
27
|
+
+extends?: string | string[]
|
|
28
|
+
+plugins?: (PluginConfig | string)[]
|
|
29
|
+
+parser?: ParserConfig
|
|
30
|
+
+parserOptions?: ParserOptions
|
|
31
|
+
+specs?: SpecConfig
|
|
32
|
+
+excludeFiles?: string[]
|
|
33
|
+
+severity?: SeverityOptions
|
|
34
|
+
+pretenders?: Pretender[] | PretenderDetails
|
|
35
|
+
+rules?: Rules
|
|
36
|
+
+nodeRules?: NodeRule[]
|
|
37
|
+
+childNodeRules?: ChildNodeRule[]
|
|
38
|
+
+overrideMode?: "merge" | "reset"
|
|
39
|
+
+overrides?: Record~string, OverrideConfig~
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
class OverrideConfig {
|
|
43
|
+
<<Omit Config NoInherit>>
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
class OptimizedConfig {
|
|
47
|
+
+plugins?: PluginConfig[]
|
|
48
|
+
+pretenders?: PretenderDetails
|
|
49
|
+
+overrides?: Record~string, OptimizedOverrideConfig~
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
Config --> OverrideConfig : "Omit $schema, extends,\noverrideMode, overrides"
|
|
53
|
+
Config --> OptimizedConfig : "mergeConfig()"
|
|
54
|
+
OverrideConfig --> OptimizedOverrideConfig : "mergeConfig()"
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
### Config to OptimizedConfig Conversion
|
|
58
|
+
|
|
59
|
+
| Field | Config | OptimizedConfig | Conversion |
|
|
60
|
+
| ------------ | --------------------------------- | ----------------------------------------- | -------------------------------------- |
|
|
61
|
+
| `plugins` | `(PluginConfig \| string)[]` | `PluginConfig[]` | Strings normalized to `{name}` objects |
|
|
62
|
+
| `pretenders` | `Pretender[] \| PretenderDetails` | `PretenderDetails` | Arrays converted to `{data: [...]}` |
|
|
63
|
+
| `extends` | `string \| string[]` | Removed | No longer needed after merging |
|
|
64
|
+
| `$schema` | `string` | Removed | Metadata only |
|
|
65
|
+
| `overrides` | `Record<string, OverrideConfig>` | `Record<string, OptimizedOverrideConfig>` | Each value recursively merged |
|
|
66
|
+
|
|
67
|
+
### Rule Type Forms
|
|
68
|
+
|
|
69
|
+
A rule can be configured in three forms:
|
|
70
|
+
|
|
71
|
+
| Form | Type | Example | Meaning |
|
|
72
|
+
| ------- | ----------------- | ------------------------------------ | ------------------------------ |
|
|
73
|
+
| Boolean | `boolean` | `true` / `false` | Enable with defaults / disable |
|
|
74
|
+
| Value | `RuleConfigValue` | `"always"`, `["a","b"]`, `null` | Shorthand value |
|
|
75
|
+
| Object | `RuleConfig<T,O>` | `{severity, value, options, reason}` | Full configuration |
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
type Rule<T, O> = RuleConfig<T, O> | Readonly<T> | boolean;
|
|
79
|
+
|
|
80
|
+
type RuleConfig<T, O> = {
|
|
81
|
+
severity?: Severity; // 'error' | 'warning' | 'info'
|
|
82
|
+
value?: Readonly<T>;
|
|
83
|
+
options?: Readonly<O>;
|
|
84
|
+
reason?: string;
|
|
85
|
+
};
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### NodeRule / ChildNodeRule
|
|
89
|
+
|
|
90
|
+
- `NodeRule` -- Targets specific nodes by CSS selector, regex selector, ARIA roles, categories, or obsolete flag, then overrides their rule settings
|
|
91
|
+
- `ChildNodeRule` -- Similar to `NodeRule` but targets child nodes; includes an `inheritance` flag to control whether overrides propagate to descendants
|
|
92
|
+
|
|
93
|
+
### Pretender Types
|
|
94
|
+
|
|
95
|
+
- `Pretender` -- Uses a CSS selector to make custom elements appear as standard elements for linting purposes; the `as` field specifies the element name or a detailed `OriginalNode`
|
|
96
|
+
- `OriginalNode` -- Defines an element's name, slots, namespace, attributes, inherited attributes, and ARIA properties
|
|
97
|
+
- `PretenderDetails` -- Normalized form `{data?, files?, imports?}` used after merging
|
|
98
|
+
|
|
99
|
+
## Merge Algorithm
|
|
100
|
+
|
|
101
|
+
This is the core of the package. The `mergeConfig()` function combines two configurations with property-specific strategies.
|
|
102
|
+
|
|
103
|
+
### mergeConfig() Overall Flow
|
|
104
|
+
|
|
105
|
+
```ts
|
|
106
|
+
mergeConfig(a: Config, b?: Config): OptimizedConfig
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
```mermaid
|
|
110
|
+
flowchart TD
|
|
111
|
+
A["Input: base (a) + override (b)"] --> B{"b provided?"}
|
|
112
|
+
B -->|Yes| C["Set deleteExtendsProp = true"]
|
|
113
|
+
B -->|No| C2["Set b = {}, deleteExtendsProp = false"]
|
|
114
|
+
C --> D["Spread: {...a, ...b}\n(primitive fields: right-side wins)"]
|
|
115
|
+
C2 --> D
|
|
116
|
+
D --> E["Apply per-property\nmerge strategies\n(see table below)"]
|
|
117
|
+
E --> F{"deleteExtendsProp?"}
|
|
118
|
+
F -->|Yes| G["Delete extends from result"]
|
|
119
|
+
F -->|No| H["Keep extends"]
|
|
120
|
+
G --> I["deleteUndefProp()\nRemove undefined properties"]
|
|
121
|
+
H --> I
|
|
122
|
+
I --> J["Return OptimizedConfig"]
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Per-Property Merge Strategy Table
|
|
126
|
+
|
|
127
|
+
| Property | Strategy | Helper Function | Details |
|
|
128
|
+
| ---------------- | -------------------------------- | ---------------------------------------------------- | ------------------------------------------------- |
|
|
129
|
+
| `plugins` | Concat + deduplicate + normalize | `concatArray(uniquely=true, comparePropName='name')` | Same-name plugins have their settings deep-merged |
|
|
130
|
+
| `parser` | Object deep merge | `mergeObject()` | Right-side wins, uses deepmerge library |
|
|
131
|
+
| `parserOptions` | Object deep merge | `mergeObject()` | Same as above |
|
|
132
|
+
| `specs` | Object deep merge | `mergeObject()` | Same as above |
|
|
133
|
+
| `excludeFiles` | Concat + deduplicate | `concatArray(uniquely=true)` | Simple value deduplication |
|
|
134
|
+
| `severity` | Object deep merge | `mergeObject()` | Same as parser |
|
|
135
|
+
| `pretenders` | Format conversion + deep merge | `mergePretenders()` | Array converted to PretenderDetails, then merged |
|
|
136
|
+
| `rules` | Per-rule merge | `mergeRules()` then `mergeRule()` | **Most complex -- see next section** |
|
|
137
|
+
| `nodeRules` | Concat (no deduplicate) | `concatArray()` | Both arrays simply concatenated |
|
|
138
|
+
| `childNodeRules` | Concat (no deduplicate) | `concatArray()` | Same as nodeRules |
|
|
139
|
+
| `overrideMode` | Right-side wins | `b.overrideMode ?? a.overrideMode` | Simple precedence |
|
|
140
|
+
| `overrides` | Per-key recursive merge | `mergeOverrides()` | Calls `mergeConfig()` recursively for each key |
|
|
141
|
+
| `extends` | Concat then delete | `concatArray()` | Removed from result after merge |
|
|
142
|
+
|
|
143
|
+
### mergeRule() -- Rule Merge Details
|
|
144
|
+
|
|
145
|
+
```ts
|
|
146
|
+
mergeRule(a: Nullable<AnyRule>, b: AnyRule): AnyRule
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
This function handles the most complex merge logic. Both inputs are first normalized via `optimizeRule()` (which handles the deprecated `option` to `options` migration).
|
|
150
|
+
|
|
151
|
+
```mermaid
|
|
152
|
+
flowchart TD
|
|
153
|
+
Start["mergeRule(a, b)"] --> OptAB["Normalize both via optimizeRule()"]
|
|
154
|
+
OptAB --> ChkFalse{"b === false OR\nb.value === false?"}
|
|
155
|
+
ChkFalse -->|Yes| RetFalse["return false\n(absolute disable)"]
|
|
156
|
+
ChkFalse -->|No| ChkAUndef{"a === undefined?"}
|
|
157
|
+
ChkAUndef -->|Yes| RetB["return b"]
|
|
158
|
+
ChkAUndef -->|No| ChkBUndef{"b === undefined?"}
|
|
159
|
+
ChkBUndef -->|Yes| RetA["return a"]
|
|
160
|
+
ChkBUndef -->|No| ChkBVal{"b is Value?\n(primitive/null/array)"}
|
|
161
|
+
ChkBVal -->|Yes| ChkAVal{"a is Value?"}
|
|
162
|
+
ChkAVal -->|Yes| ChkBothArr{"Both arrays?"}
|
|
163
|
+
ChkBothArr -->|Yes| RetConcat["return [...a, ...b]\n(concatenate)"]
|
|
164
|
+
ChkBothArr -->|No| RetBVal["return b\n(right-side wins)"]
|
|
165
|
+
ChkAVal -->|No| MergeValObj["Keep a's severity/reason\nReplace value\n(arrays concatenated)"]
|
|
166
|
+
ChkBVal -->|No| MergeObj["severity: b ?? a\nvalue: b ?? a\noptions: mergeObject(a, b)\nreason: b ?? a"]
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
**Key Design Decisions:**
|
|
170
|
+
|
|
171
|
+
1. **`false` is absolute disable** -- If the override is `false` (or `{value: false}`), the result is always `false`, regardless of what the base config says
|
|
172
|
+
2. **Array values are concatenated** -- `["a","b"]` + `["c","d"]` results in `["a","b","c","d"]`, enabling incremental rule additions across extends chains
|
|
173
|
+
3. **options uses deep merge** -- While severity, value, and reason use right-side-wins precedence, options alone uses `mergeObject()` (deep merge via deepmerge library)
|
|
174
|
+
|
|
175
|
+
### Helper Functions
|
|
176
|
+
|
|
177
|
+
#### concatArray(a, b, uniquely?, comparePropName?)
|
|
178
|
+
|
|
179
|
+
Concatenates two arrays with optional deduplication:
|
|
180
|
+
|
|
181
|
+
- `uniquely=false` -- Simple concatenation, no deduplication
|
|
182
|
+
- `uniquely=true`, no `comparePropName` -- Exact-match deduplication
|
|
183
|
+
- `uniquely=true`, with `comparePropName` -- Deduplicates by the specified property name; when two objects share the same name, they are merged via `mergeObject()` (e.g., plugin settings)
|
|
184
|
+
- Returns `undefined` for empty results
|
|
185
|
+
|
|
186
|
+
#### mergeObject(a, b)
|
|
187
|
+
|
|
188
|
+
Deep merges two objects using the `deepmerge` library. Right-side values take precedence. Removes undefined properties from the result.
|
|
189
|
+
|
|
190
|
+
#### mergeOverrides(a, b)
|
|
191
|
+
|
|
192
|
+
Collects the union of all keys from both override records. For each key, calls `mergeConfig(a[key], b[key])` recursively. Removes `$schema`, `extends`, and `overrides` from each result (since these are top-level-only properties).
|
|
193
|
+
|
|
194
|
+
#### mergePretenders(a, b)
|
|
195
|
+
|
|
196
|
+
Converts array-form pretenders to the normalized `PretenderDetails` form (`{data: [...]}`) before deep merging with `mergeObject()`.
|
|
197
|
+
|
|
198
|
+
## Template Rendering System
|
|
199
|
+
|
|
200
|
+
### provideValue(template, data)
|
|
201
|
+
|
|
202
|
+
Renders a Mustache template string with the provided data:
|
|
203
|
+
|
|
204
|
+
- No variables in template -- Returns the template unchanged
|
|
205
|
+
- Variables present but no matching keys in data -- Returns `undefined`
|
|
206
|
+
- Variables present with matching keys -- Returns the rendered result
|
|
207
|
+
|
|
208
|
+
### exchangeValueOnRule(rule, data)
|
|
209
|
+
|
|
210
|
+
Applies Mustache template rendering to all string values within a rule configuration:
|
|
211
|
+
|
|
212
|
+
- **value** -- String values are rendered; array elements are individually rendered
|
|
213
|
+
- **options** -- Recursively renders all string values in the options object
|
|
214
|
+
- **reason** -- Rendered as a string
|
|
215
|
+
|
|
216
|
+
This function is used by `nodeRules` and `childNodeRules` with `regexSelector`, where captured groups (`$0`, `$1`, named captures like `dataName`) are injected as template variables into rule settings.
|
|
217
|
+
|
|
218
|
+
## Utility Functions
|
|
219
|
+
|
|
220
|
+
| Function | Purpose |
|
|
221
|
+
| --------------------- | --------------------------------------------------------------------------------------------------------- |
|
|
222
|
+
| `cleanOptions()` | Normalizes deprecated `option` field to `options`, extracts standard fields, removes undefined properties |
|
|
223
|
+
| `isRuleConfigValue()` | Type guard: returns `true` for primitives, `null`, and arrays (i.e., not a `RuleConfig` object) |
|
|
224
|
+
| `deleteUndefProp()` | Removes all properties with `undefined` values from a plain object in-place |
|
|
225
|
+
|
|
226
|
+
## Key Source Files
|
|
227
|
+
|
|
228
|
+
| File | Purpose |
|
|
229
|
+
| --------------------- | ------------------------------------------------------------------------------------------------------- |
|
|
230
|
+
| `src/types.ts` | All type definitions (Config, Rule, Pretender, Violation, etc.) |
|
|
231
|
+
| `src/merge-config.ts` | `mergeConfig()`, `mergeRule()`, and all helper functions |
|
|
232
|
+
| `src/utils.ts` | `provideValue()`, `exchangeValueOnRule()`, `cleanOptions()`, `isRuleConfigValue()`, `deleteUndefProp()` |
|
|
233
|
+
| `src/index.ts` | Re-exports all public APIs |
|
|
234
|
+
|
|
235
|
+
## External Dependencies
|
|
236
|
+
|
|
237
|
+
| Dependency | Purpose |
|
|
238
|
+
| ---------------------- | ------------------------------------------------- |
|
|
239
|
+
| `@markuplint/ml-ast` | `ParserOptions` type (type-only) |
|
|
240
|
+
| `@markuplint/selector` | `RegexSelector` type (re-exported) |
|
|
241
|
+
| `@markuplint/shared` | `Nullable` utility type |
|
|
242
|
+
| `deepmerge` | Deep merge implementation used by `mergeObject()` |
|
|
243
|
+
| `is-plain-object` | Plain object detection in `deleteUndefProp()` |
|
|
244
|
+
| `mustache` | Template rendering engine for `provideValue()` |
|
|
245
|
+
| `type-fest` | `Writable` utility type |
|
|
246
|
+
|
|
247
|
+
## Integration Points
|
|
248
|
+
|
|
249
|
+
```mermaid
|
|
250
|
+
flowchart LR
|
|
251
|
+
subgraph upstream ["Upstream"]
|
|
252
|
+
fileResolver["@markuplint/file-resolver\n(reads config files,\nresolves extends chain)"]
|
|
253
|
+
end
|
|
254
|
+
|
|
255
|
+
subgraph pkg ["@markuplint/ml-config"]
|
|
256
|
+
mergeConfig["mergeConfig()\n(merge algorithm)"]
|
|
257
|
+
types["Config types"]
|
|
258
|
+
templates["Template rendering"]
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
subgraph downstream ["Downstream"]
|
|
262
|
+
mlCore["@markuplint/ml-core\n(applies rules using\nOptimizedConfig)"]
|
|
263
|
+
rules["@markuplint/rules\n(uses Rule<T,O>,\nRuleConfig<T,O> types)"]
|
|
264
|
+
end
|
|
265
|
+
|
|
266
|
+
fileResolver -->|"calls mergeConfig()\nfor each extends layer"| mergeConfig
|
|
267
|
+
mergeConfig -->|"produces OptimizedConfig"| mlCore
|
|
268
|
+
types -->|"Rule, RuleConfig types"| rules
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
### Upstream
|
|
272
|
+
|
|
273
|
+
- **`@markuplint/file-resolver`** -- Reads configuration files, resolves the extends chain, and calls `mergeConfig()` to combine layers
|
|
274
|
+
|
|
275
|
+
### Downstream
|
|
276
|
+
|
|
277
|
+
- **`@markuplint/ml-core`** -- Receives the merged `OptimizedConfig` and applies rules to the parsed document
|
|
278
|
+
- **`@markuplint/rules`** -- Uses `Rule<T,O>` and `RuleConfig<T,O>` types to define rule implementations
|
|
279
|
+
|
|
280
|
+
## Documentation Map
|
|
281
|
+
|
|
282
|
+
- [Maintenance Guide](docs/maintenance.md) -- Commands, recipes, and troubleshooting
|
package/CHANGELOG.md
CHANGED
|
@@ -3,13 +3,17 @@
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
|
|
5
5
|
|
|
6
|
-
## [4.8.
|
|
6
|
+
## [4.8.15](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.14...@markuplint/ml-config@4.8.15) (2026-02-10)
|
|
7
7
|
|
|
8
8
|
**Note:** Version bump only for package @markuplint/ml-config
|
|
9
9
|
|
|
10
|
+
## [4.8.14](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.13...@markuplint/ml-config@4.8.14) (2025-11-05)
|
|
10
11
|
|
|
12
|
+
**Note:** Version bump only for package @markuplint/ml-config
|
|
11
13
|
|
|
14
|
+
## [4.8.13](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.12...@markuplint/ml-config@4.8.13) (2025-08-24)
|
|
12
15
|
|
|
16
|
+
**Note:** Version bump only for package @markuplint/ml-config
|
|
13
17
|
|
|
14
18
|
## [4.8.12](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.11...@markuplint/ml-config@4.8.12) (2025-08-13)
|
|
15
19
|
|