@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/SKILL.md ADDED
@@ -0,0 +1,114 @@
1
+ ---
2
+ description: Maintenance tasks for @markuplint/ml-config
3
+ globs:
4
+ - packages/@markuplint/ml-config/src/**/*.ts
5
+ alwaysApply: false
6
+ ---
7
+
8
+ # ml-config-maintenance
9
+
10
+ Perform maintenance tasks for `@markuplint/ml-config`: add config properties,
11
+ modify merge strategies, and update rule merge logic.
12
+
13
+ ## Input
14
+
15
+ `$ARGUMENTS` specifies the task. Supported tasks:
16
+
17
+ | Task | Description |
18
+ | --------------------- | ---------------------------------------------------------------------- |
19
+ | `add-config-property` | Add a new property to the Config type and implement its merge strategy |
20
+ | `add-merge-strategy` | Change an existing property's merge strategy |
21
+ | `modify-rule-merge` | Modify the rule merge logic in mergeRule() |
22
+
23
+ If omitted, defaults to `add-config-property`.
24
+
25
+ ## Reference
26
+
27
+ Before executing any task, read `docs/maintenance.md` (or `docs/maintenance.ja.md`)
28
+ for the full guide. The recipes there are the source of truth for procedures.
29
+
30
+ Also read:
31
+
32
+ - `ARCHITECTURE.md` -- Package overview, type system, merge algorithm details, and template rendering
33
+ - `src/types.ts` -- All type definitions (source of truth for Config, Rule, Pretender types)
34
+ - `src/merge-config.ts` -- Merge algorithm (source of truth for mergeConfig, mergeRule, helpers)
35
+ - `src/utils.ts` -- Template rendering and utility functions
36
+
37
+ ## Task: add-config-property
38
+
39
+ Add a new property to the Config type and implement its merge strategy. Follow recipe #1 in `docs/maintenance.md`.
40
+
41
+ ### Step 1: Define the type
42
+
43
+ 1. Read `src/types.ts`
44
+ 2. Add the readonly property to the `Config` type
45
+ 3. If the property needs a different type in the optimized form, update `OptimizedConfig` (use `Omit` + re-define)
46
+ 4. Decide whether to include it in `OverrideConfig` (add to `NoInherit` if it should be top-level only)
47
+
48
+ ### Step 2: Implement the merge strategy
49
+
50
+ 1. Read `src/merge-config.ts`
51
+ 2. Add the merge logic inside `mergeConfig()`, choosing the appropriate strategy:
52
+ - `mergeObject()` for object deep merge
53
+ - `concatArray()` for array concatenation
54
+ - `b.prop ?? a.prop` for simple right-side precedence
55
+ 3. If the property needs format conversion (like plugins or pretenders), implement a conversion helper
56
+
57
+ ### Step 3: Verify
58
+
59
+ 1. Add test cases in `src/merge-config.spec.ts`
60
+ 2. Build: `yarn build --scope @markuplint/ml-config`
61
+ 3. Test: `yarn test --scope @markuplint/ml-config`
62
+
63
+ ## Task: add-merge-strategy
64
+
65
+ Change an existing property's merge strategy. Follow recipe #2 in `docs/maintenance.md`.
66
+
67
+ ### Step 1: Understand the current strategy
68
+
69
+ 1. Read `src/merge-config.ts` and locate the property in `mergeConfig()`
70
+ 2. Read `ARCHITECTURE.md` section "Per-Property Merge Strategy Table" for context
71
+
72
+ ### Step 2: Modify the strategy
73
+
74
+ 1. Replace the current merge call with the new strategy
75
+ 2. Available strategies: `mergeObject()`, `concatArray()`, `b.prop ?? a.prop`, or a custom helper
76
+
77
+ ### Step 3: Verify
78
+
79
+ 1. Update existing tests or add new ones in `src/merge-config.spec.ts`
80
+ 2. Build: `yarn build --scope @markuplint/ml-config`
81
+ 3. Test: `yarn test --scope @markuplint/ml-config`
82
+
83
+ ## Task: modify-rule-merge
84
+
85
+ Modify the rule merge logic in `mergeRule()`. Follow recipe #3 in `docs/maintenance.md`.
86
+
87
+ ### Step 1: Understand the current flow
88
+
89
+ 1. Read `src/merge-config.ts` and locate the `mergeRule()` function
90
+ 2. Current flow: `false` check -> `undefined` checks -> value type check -> object type merge
91
+ 3. Read `ARCHITECTURE.md` section "mergeRule()" for the detailed flowchart
92
+
93
+ ### Step 2: Modify the logic
94
+
95
+ 1. Make changes to `mergeRule()`, paying attention to:
96
+ - The `false` absolute disable behavior
97
+ - Array concatenation for value arrays
98
+ - Deep merge for options via `mergeObject()`
99
+ - Right-side precedence for severity, value, reason
100
+
101
+ ### Step 3: Verify
102
+
103
+ 1. Verify existing tests still pass (especially the edge cases in `src/merge-config.spec.ts`)
104
+ 2. Add new test cases for the modified behavior
105
+ 3. Build: `yarn build --scope @markuplint/ml-config`
106
+ 4. Test: `yarn test --scope @markuplint/ml-config`
107
+
108
+ ## Rules
109
+
110
+ 1. **All Config properties are `readonly`** -- use `readonly` for all fields in type definitions.
111
+ 2. **Choose merge strategies carefully** -- refer to the strategy table in `ARCHITECTURE.md` when deciding how to merge a new property.
112
+ 3. **Test merge edge cases** -- always test with `undefined`, empty objects, and conflicting values.
113
+ 4. **Run `deleteUndefProp()` after merge** -- ensure no `undefined` properties leak into the result.
114
+ 5. **Add JSDoc comments** to all new public types and functions.
@@ -0,0 +1,180 @@
1
+ # メンテナンスガイド
2
+
3
+ ## コマンド
4
+
5
+ | コマンド | 説明 |
6
+ | ------------------------------------------ | ---------------------- |
7
+ | `yarn build --scope @markuplint/ml-config` | このパッケージをビルド |
8
+ | `yarn dev --scope @markuplint/ml-config` | ウォッチモードでビルド |
9
+ | `yarn clean --scope @markuplint/ml-config` | ビルド成果物を削除 |
10
+ | `yarn test --scope @markuplint/ml-config` | テストを実行 |
11
+
12
+ ## テスト
13
+
14
+ テストファイルは `*.spec.ts` の命名規則に従い、`src/` ディレクトリに配置されています:
15
+
16
+ | テストファイル | カバレッジ |
17
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------- |
18
+ | `merge-config.spec.ts` | `mergeConfig()` 統合テスト(plugins, parser, overrides, rules)、`mergeRule()` エッジケース、pretender マージ |
19
+ | `utils.spec.ts` | `provideValue()` テンプレートレンダリング、`exchangeValueOnRule()` の value/options/reason 処理 |
20
+
21
+ マージテストの主なパターン:
22
+
23
+ ```ts
24
+ import { mergeConfig, mergeRule } from './merge-config.js';
25
+
26
+ expect(mergeConfig(baseConfig, overrideConfig)).toStrictEqual({
27
+ // 期待されるマージ結果
28
+ });
29
+ ```
30
+
31
+ ルールマージのテスト:
32
+
33
+ ```ts
34
+ expect(mergeRule(baseRule, overrideRule)).toStrictEqual({
35
+ // 期待されるマージ済みルール
36
+ });
37
+ ```
38
+
39
+ テンプレートレンダリングのテスト:
40
+
41
+ ```ts
42
+ import { provideValue, exchangeValueOnRule } from './utils.js';
43
+
44
+ expect(provideValue('{{ dataName }}', { dataName: 'value' })).toBe('value');
45
+ expect(exchangeValueOnRule({ value: '{{ var }}' }, { var: 'x' })).toStrictEqual({ value: 'x' });
46
+ ```
47
+
48
+ ## レシピ
49
+
50
+ ### 1. Config 型への新しいプロパティ追加
51
+
52
+ 1. `src/types.ts` を読み、`Config` 型を確認
53
+ 2. `Config` に新しい readonly プロパティを追加:
54
+ ```ts
55
+ readonly newProp?: NewPropType;
56
+ ```
57
+ 3. `OptimizedConfig` に含めるかどうかを判断:
58
+ - マージ後に型が変わる場合(plugins の string -> object のように)、`Omit` + 再定義を使用
59
+ - 型が同じなら `OptimizedConfig` のスプレッドで自動的に継承される
60
+ 4. `OverrideConfig` に含めるかどうかを判断:
61
+ - トップレベル専用(`$schema`、`extends` のように)なら、`NoInherit` ユニオン型にプロパティ名を追加
62
+ - ファイルパターンごとにオーバーライド可能にする場合はそのまま(`Omit<Config, NoInherit>` 経由で `Config` から継承)
63
+ 5. `src/merge-config.ts` を読み、`mergeConfig()` 関数の config オブジェクト内にマージロジックを追加:
64
+ - オブジェクト deep merge: `newProp: mergeObject(a.newProp, b.newProp)`
65
+ - 配列結合: `newProp: concatArray(a.newProp, b.newProp)`
66
+ - 配列結合+重複排除: `newProp: concatArray(a.newProp, b.newProp, true)`
67
+ - 単純な右辺優先: `newProp: b.newProp ?? a.newProp`(スプレッドで処理されるが、明示的な方が分かりやすい)
68
+ 6. `src/merge-config.spec.ts` にテストケースを追加
69
+ 7. ビルド: `yarn build --scope @markuplint/ml-config`
70
+ 8. テスト: `yarn test --scope @markuplint/ml-config`
71
+
72
+ ### 2. マージ戦略の変更
73
+
74
+ 1. `src/merge-config.ts` を読み、`mergeConfig()` 関数内のプロパティを確認
75
+ 2. 現在の戦略を特定(`ARCHITECTURE.md` の戦略テーブルを参照)
76
+ 3. マージ呼び出しを置換。利用可能な戦略:
77
+ - `mergeObject(a.prop, b.prop)` -- 右辺優先の deep merge
78
+ - `concatArray(a.prop, b.prop)` -- 単純な配列結合
79
+ - `concatArray(a.prop, b.prop, true)` -- 重複排除付き結合
80
+ - `concatArray(a.prop, b.prop, true, 'name')` -- 名前付きプロパティで重複排除、同名オブジェクトをマージ
81
+ - `b.prop ?? a.prop` -- 単純な右辺優先(マージなし)
82
+ - カスタムヘルパー関数(複雑な変換用)
83
+ 4. `src/merge-config.spec.ts` のテストを更新または追加
84
+ 5. ビルド: `yarn build --scope @markuplint/ml-config`
85
+ 6. テスト: `yarn test --scope @markuplint/ml-config`
86
+
87
+ ### 3. ルールマージロジックの変更
88
+
89
+ 1. `src/merge-config.ts` を読み、`mergeRule()` 関数を確認
90
+ 2. 現在のフローを理解:
91
+ - `optimizeRule()` が両方の入力を正規化(非推奨の `option` -> `options` を処理)
92
+ - `false` チェック: override が `false` または `{value: false}` なら常に `false` を返す
93
+ - `undefined` チェック: 片方がない場合はもう片方を返す
94
+ - 値型チェック: override が直接値(primitive/null/array)なら置換または連結
95
+ - オブジェクト型マージ: severity/value/reason は右辺優先、options は deep merge
96
+ 3. 変更を加える際、主要な不変条件を保持:
97
+ - `false` は常に絶対無効化になる必要がある
98
+ - 配列値は連結される(置換ではない)
99
+ - `options` は `mergeObject()` による deep merge が必要
100
+ 4. `src/merge-config.spec.ts` の既存テストが通ることを確認
101
+ 5. 変更後の動作に対する新しいテストケースを追加
102
+ 6. ビルド: `yarn build --scope @markuplint/ml-config`
103
+ 7. テスト: `yarn test --scope @markuplint/ml-config`
104
+
105
+ ### 4. Pretender 型の拡張
106
+
107
+ 1. `src/types.ts` を読み、`Pretender`、`PretenderDetails`、`OriginalNode` を確認
108
+ 2. 適切な型に新しいフィールドを追加
109
+ 3. `src/merge-config.ts` を読み、`mergePretenders()` を確認:
110
+ - 配列形式を `{data: [...]}` に変換(`convertPretenersToDetails()`)
111
+ - `mergeObject()` で deep merge
112
+ - `PretenderDetails` の新しいフィールドは自動的に deep merge される
113
+ - `Pretender`(`data` 配列内)の新しいフィールドは deepmerge の配列マージで処理
114
+ 4. `src/merge-config.spec.ts` にテストケースを追加
115
+ 5. ビルド: `yarn build --scope @markuplint/ml-config`
116
+ 6. テスト: `yarn test --scope @markuplint/ml-config`
117
+
118
+ ## 上流パッケージ影響チェックリスト
119
+
120
+ 上流パッケージの変更がこのパッケージに影響する可能性があります:
121
+
122
+ | パッケージ | ml-config への影響 |
123
+ | ---------------------- | ---------------------------------------------------- |
124
+ | `@markuplint/ml-ast` | `ParserOptions` 型の変更 |
125
+ | `@markuplint/selector` | `RegexSelector` 型の変更(再エクスポートされている) |
126
+ | `@markuplint/shared` | `Nullable` ユーティリティ型の変更 |
127
+
128
+ 上流パッケージが更新された場合:
129
+
130
+ ```shell
131
+ yarn test --scope @markuplint/ml-config
132
+ ```
133
+
134
+ ## トラブルシューティング
135
+
136
+ ### マージ後にプロパティが消える
137
+
138
+ **症状:** 入力設定にプロパティが存在するが、`mergeConfig()` の結果に含まれない。
139
+
140
+ **原因:** `deleteUndefProp()` が `undefined` 値のプロパティをすべて除去している。マージロジックがそのプロパティに対して `undefined` を生成している可能性がある。
141
+
142
+ **解決策:**
143
+
144
+ 1. `mergeConfig()` 内のそのプロパティのマージ戦略を確認
145
+ 2. 両方の入力に値がある場合にヘルパー関数が `undefined` を返していないか検証
146
+ 3. `concatArray()` は空配列に対して `undefined` を返す -- 入力が空でないことを確認
147
+
148
+ ### ルール値が上書きではなく連結される
149
+
150
+ **症状:** ルールの配列値が上書きされずに増え続ける。
151
+
152
+ **原因:** `mergeRule()` はベースとオーバーライドの両方が配列の場合、設計上連結する。
153
+
154
+ **解決策:** 配列値を完全に上書きするには、override 側でオブジェクト形式を使用:
155
+
156
+ ```json
157
+ { "value": ["new", "values"], "options": {} }
158
+ ```
159
+
160
+ これにより連結ではなく値が完全に置換される。
161
+
162
+ ### プラグインの settings がマージされない
163
+
164
+ **症状:** 同名プラグインの2つの設定で、片方の settings しか反映されない。
165
+
166
+ **原因:** 片方が文字列形式(`"plugin-name"`)、もう片方がオブジェクト形式(`{name: "plugin-name", settings: {...}}`)の場合、文字列形式にはマージする settings がない。
167
+
168
+ **解決策:** 両方でオブジェクト形式を使用:
169
+
170
+ ```json
171
+ { "name": "plugin-name", "settings": { "key": "value" } }
172
+ ```
173
+
174
+ ### false でルールを無効化できない
175
+
176
+ **症状:** ローカル設定でルールを `false` に設定しても、extends を使用していると無効化されない。
177
+
178
+ **原因:** `mergeConfig()` の呼び出し順序が逆になっている可能性がある。ローカル設定は第2引数(override)でなければならない。
179
+
180
+ **解決策:** 呼び出し順序が `mergeConfig(extendedConfig, localConfig)` であることを確認。ローカル設定が右辺(override)引数になるようにする。
@@ -0,0 +1,180 @@
1
+ # Maintenance Guide
2
+
3
+ ## Commands
4
+
5
+ | Command | Description |
6
+ | ------------------------------------------ | ---------------------- |
7
+ | `yarn build --scope @markuplint/ml-config` | Build this package |
8
+ | `yarn dev --scope @markuplint/ml-config` | Watch mode build |
9
+ | `yarn clean --scope @markuplint/ml-config` | Remove build artifacts |
10
+ | `yarn test --scope @markuplint/ml-config` | Run tests |
11
+
12
+ ## Testing
13
+
14
+ Test files follow the `*.spec.ts` naming convention and are located in the `src/` directory:
15
+
16
+ | Test File | Coverage |
17
+ | ---------------------- | ------------------------------------------------------------------------------------------------------------ |
18
+ | `merge-config.spec.ts` | `mergeConfig()` integration (plugins, parser, overrides, rules), `mergeRule()` edge cases, pretender merging |
19
+ | `utils.spec.ts` | `provideValue()` template rendering, `exchangeValueOnRule()` with value/options/reason |
20
+
21
+ The primary testing pattern for merge tests:
22
+
23
+ ```ts
24
+ import { mergeConfig, mergeRule } from './merge-config.js';
25
+
26
+ expect(mergeConfig(baseConfig, overrideConfig)).toStrictEqual({
27
+ // expected merged result
28
+ });
29
+ ```
30
+
31
+ For rule merge tests:
32
+
33
+ ```ts
34
+ expect(mergeRule(baseRule, overrideRule)).toStrictEqual({
35
+ // expected merged rule
36
+ });
37
+ ```
38
+
39
+ For template rendering tests:
40
+
41
+ ```ts
42
+ import { provideValue, exchangeValueOnRule } from './utils.js';
43
+
44
+ expect(provideValue('{{ dataName }}', { dataName: 'value' })).toBe('value');
45
+ expect(exchangeValueOnRule({ value: '{{ var }}' }, { var: 'x' })).toStrictEqual({ value: 'x' });
46
+ ```
47
+
48
+ ## Recipes
49
+
50
+ ### 1. Adding a New Property to Config
51
+
52
+ 1. Read `src/types.ts` and locate the `Config` type
53
+ 2. Add the new readonly property to `Config`:
54
+ ```ts
55
+ readonly newProp?: NewPropType;
56
+ ```
57
+ 3. Decide whether it belongs in `OptimizedConfig`:
58
+ - If the type changes after merging (like plugins string -> object), use `Omit` + re-define in `OptimizedConfig`
59
+ - If it stays the same, it is inherited automatically via the spread in `OptimizedConfig`
60
+ 4. Decide whether it belongs in `OverrideConfig`:
61
+ - If it should be top-level only (like `$schema`, `extends`), add the property name to the `NoInherit` union type
62
+ - If it should be overridable per file pattern, leave it as-is (it inherits from `Config` via `Omit<Config, NoInherit>`)
63
+ 5. Read `src/merge-config.ts` and add the merge logic inside the `mergeConfig()` function's config object:
64
+ - For object deep merge: `newProp: mergeObject(a.newProp, b.newProp)`
65
+ - For array concatenation: `newProp: concatArray(a.newProp, b.newProp)`
66
+ - For array with deduplication: `newProp: concatArray(a.newProp, b.newProp, true)`
67
+ - For simple right-side precedence: `newProp: b.newProp ?? a.newProp` (handled by the spread, but explicit is clearer)
68
+ 6. Add test cases in `src/merge-config.spec.ts`
69
+ 7. Build: `yarn build --scope @markuplint/ml-config`
70
+ 8. Test: `yarn test --scope @markuplint/ml-config`
71
+
72
+ ### 2. Changing a Merge Strategy
73
+
74
+ 1. Read `src/merge-config.ts` and locate the property in the `mergeConfig()` function
75
+ 2. Identify the current strategy (see the strategy table in `ARCHITECTURE.md`)
76
+ 3. Replace the merge call. Available strategies:
77
+ - `mergeObject(a.prop, b.prop)` -- Deep merge with right-side precedence
78
+ - `concatArray(a.prop, b.prop)` -- Simple array concatenation
79
+ - `concatArray(a.prop, b.prop, true)` -- Concat with deduplication
80
+ - `concatArray(a.prop, b.prop, true, 'name')` -- Concat with deduplication by named property, merging same-name objects
81
+ - `b.prop ?? a.prop` -- Simple right-side precedence (no merge)
82
+ - Custom helper function for complex transformations
83
+ 4. Update or add tests in `src/merge-config.spec.ts`
84
+ 5. Build: `yarn build --scope @markuplint/ml-config`
85
+ 6. Test: `yarn test --scope @markuplint/ml-config`
86
+
87
+ ### 3. Modifying Rule Merge Logic
88
+
89
+ 1. Read `src/merge-config.ts` and locate the `mergeRule()` function
90
+ 2. Understand the current flow:
91
+ - `optimizeRule()` normalizes both inputs (handles deprecated `option` -> `options`)
92
+ - `false` check: override `false` or `{value: false}` always returns `false`
93
+ - `undefined` checks: missing side returns the other side
94
+ - Value type check: if override is a direct value (primitive/null/array), it replaces or concatenates
95
+ - Object type merge: severity/value/reason use right-side precedence, options use deep merge
96
+ 3. Make changes, preserving the key invariants:
97
+ - `false` must always result in absolute disable
98
+ - Array values must be concatenated (not replaced)
99
+ - `options` must use deep merge via `mergeObject()`
100
+ 4. Verify existing tests pass in `src/merge-config.spec.ts`
101
+ 5. Add new test cases for the modified behavior
102
+ 6. Build: `yarn build --scope @markuplint/ml-config`
103
+ 7. Test: `yarn test --scope @markuplint/ml-config`
104
+
105
+ ### 4. Extending Pretender Types
106
+
107
+ 1. Read `src/types.ts` and locate `Pretender`, `PretenderDetails`, and `OriginalNode`
108
+ 2. Add new fields to the appropriate type
109
+ 3. Read `src/merge-config.ts` and check `mergePretenders()`:
110
+ - It converts array form to `{data: [...]}` via `convertPretenersToDetails()`
111
+ - Then deep merges with `mergeObject()`
112
+ - New fields on `PretenderDetails` are automatically deep merged
113
+ - New fields on `Pretender` (inside `data` array) are handled by deepmerge's array merge
114
+ 4. Add test cases in `src/merge-config.spec.ts`
115
+ 5. Build: `yarn build --scope @markuplint/ml-config`
116
+ 6. Test: `yarn test --scope @markuplint/ml-config`
117
+
118
+ ## Upstream Impact Checklist
119
+
120
+ Changes to upstream packages can affect this package:
121
+
122
+ | Package | Impact on ml-config |
123
+ | ---------------------- | ------------------------------------------ |
124
+ | `@markuplint/ml-ast` | `ParserOptions` type changes |
125
+ | `@markuplint/selector` | `RegexSelector` type changes (re-exported) |
126
+ | `@markuplint/shared` | `Nullable` utility type changes |
127
+
128
+ When upstream packages are updated, run:
129
+
130
+ ```shell
131
+ yarn test --scope @markuplint/ml-config
132
+ ```
133
+
134
+ ## Troubleshooting
135
+
136
+ ### Properties disappear after merge
137
+
138
+ **Symptom:** A property exists in the input config but is missing from the `mergeConfig()` result.
139
+
140
+ **Cause:** `deleteUndefProp()` removes all properties with `undefined` values. The merge logic may be producing `undefined` for the property.
141
+
142
+ **Solution:**
143
+
144
+ 1. Check the merge strategy for the property in `mergeConfig()`
145
+ 2. Verify the helper function does not return `undefined` when both inputs have values
146
+ 3. `concatArray()` returns `undefined` for empty arrays -- ensure the inputs are not empty
147
+
148
+ ### Rule values are concatenated instead of replaced
149
+
150
+ **Symptom:** A rule's array value keeps growing instead of being overwritten.
151
+
152
+ **Cause:** `mergeRule()` concatenates array values by design when both base and override are arrays.
153
+
154
+ **Solution:** To completely replace an array value, use the object form in the override:
155
+
156
+ ```json
157
+ { "value": ["new", "values"], "options": {} }
158
+ ```
159
+
160
+ This replaces the value entirely instead of concatenating.
161
+
162
+ ### Plugin settings are not merged
163
+
164
+ **Symptom:** Two configs with the same plugin name result in settings from only one side.
165
+
166
+ **Cause:** If one side uses the string form (`"plugin-name"`) and the other uses the object form (`{name: "plugin-name", settings: {...}}`), the string form has no settings to merge.
167
+
168
+ **Solution:** Use the object form on both sides:
169
+
170
+ ```json
171
+ { "name": "plugin-name", "settings": { "key": "value" } }
172
+ ```
173
+
174
+ ### Rule cannot be disabled with false
175
+
176
+ **Symptom:** Setting a rule to `false` in a local config does not disable it when using extends.
177
+
178
+ **Cause:** The `mergeConfig()` call order may be reversed. The local config must be the second argument (override).
179
+
180
+ **Solution:** Ensure the call order is `mergeConfig(extendedConfig, localConfig)` where the local config is the right-side (override) argument.
@@ -1,4 +1,28 @@
1
1
  import type { Config, AnyRule, AnyRuleV2, OptimizedConfig } from './types.js';
2
2
  import type { Nullable } from '@markuplint/shared';
3
+ /**
4
+ * Deep-merges two markuplint configurations into an optimized result.
5
+ *
6
+ * Plugins, arrays, and rules are merged with specific strategies:
7
+ * - Plugins are concatenated and deduplicated by name
8
+ * - Arrays (excludeFiles, nodeRules, childNodeRules) are concatenated
9
+ * - Rules are merged per-key with right-side precedence
10
+ * - The `extends` property is removed from the result when `b` is provided
11
+ *
12
+ * @param a - The base configuration
13
+ * @param b - The configuration to merge on top of `a`
14
+ * @returns The merged and optimized configuration
15
+ */
3
16
  export declare function mergeConfig(a: Config, b?: Config): OptimizedConfig;
17
+ /**
18
+ * Merges two rule configurations with right-side precedence.
19
+ *
20
+ * If `b` is `false`, the rule is unconditionally disabled.
21
+ * If `b` is a direct value, it replaces or extends `a`.
22
+ * If both are full config objects, their properties are merged.
23
+ *
24
+ * @param a - The base rule configuration (may be `null` or `undefined`)
25
+ * @param b - The rule configuration to merge on top
26
+ * @returns The merged rule configuration
27
+ */
4
28
  export declare function mergeRule(a: Nullable<AnyRule | AnyRuleV2>, b: AnyRule | AnyRuleV2): AnyRule;
@@ -1,5 +1,18 @@
1
1
  import deepmerge from 'deepmerge';
2
2
  import { deleteUndefProp, cleanOptions, isRuleConfigValue } from './utils.js';
3
+ /**
4
+ * Deep-merges two markuplint configurations into an optimized result.
5
+ *
6
+ * Plugins, arrays, and rules are merged with specific strategies:
7
+ * - Plugins are concatenated and deduplicated by name
8
+ * - Arrays (excludeFiles, nodeRules, childNodeRules) are concatenated
9
+ * - Rules are merged per-key with right-side precedence
10
+ * - The `extends` property is removed from the result when `b` is provided
11
+ *
12
+ * @param a - The base configuration
13
+ * @param b - The configuration to merge on top of `a`
14
+ * @returns The merged and optimized configuration
15
+ */
3
16
  export function mergeConfig(a, b) {
4
17
  const deleteExtendsProp = !!b;
5
18
  b = b ?? {};
@@ -36,6 +49,17 @@ export function mergeConfig(a, b) {
36
49
  deleteUndefProp(config);
37
50
  return config;
38
51
  }
52
+ /**
53
+ * Merges two rule configurations with right-side precedence.
54
+ *
55
+ * If `b` is `false`, the rule is unconditionally disabled.
56
+ * If `b` is a direct value, it replaces or extends `a`.
57
+ * If both are full config objects, their properties are merged.
58
+ *
59
+ * @param a - The base rule configuration (may be `null` or `undefined`)
60
+ * @param b - The rule configuration to merge on top
61
+ * @returns The merged rule configuration
62
+ */
39
63
  export function mergeRule(a, b) {
40
64
  const oA = optimizeRule(a);
41
65
  const oB = optimizeRule(b);