@markuplint/ml-config 4.8.14 → 5.0.0-alpha.0
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 +314 -0
- package/ARCHITECTURE.md +314 -0
- package/CHANGELOG.md +41 -2
- package/SKILL.md +114 -0
- package/docs/maintenance.ja.md +178 -0
- package/docs/maintenance.md +178 -0
- package/lib/index.d.ts +1 -1
- package/lib/index.js +0 -1
- package/lib/merge-config.d.ts +28 -2
- package/lib/merge-config.js +106 -23
- package/lib/types.d.ts +211 -20
- package/lib/utils.d.ts +50 -9
- package/lib/utils.js +59 -22
- package/package.json +10 -8
package/CHANGELOG.md
CHANGED
|
@@ -3,13 +3,52 @@
|
|
|
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
|
-
|
|
6
|
+
# [5.0.0-alpha.0](https://github.com/markuplint/markuplint/compare/v4.14.1...v5.0.0-alpha.0) (2026-02-20)
|
|
7
7
|
|
|
8
|
-
|
|
8
|
+
### Bug Fixes
|
|
9
|
+
|
|
10
|
+
- use explicit `export type` for type-only re-exports ([7c77c05](https://github.com/markuplint/markuplint/commit/7c77c05619518c8d18a183132040f5b2cd0ab6ec))
|
|
11
|
+
|
|
12
|
+
### Code Refactoring
|
|
13
|
+
|
|
14
|
+
- **ml-config:** remove deprecated rule types ([e5d2b2d](https://github.com/markuplint/markuplint/commit/e5d2b2d6b5d7f6a060e1ea2160be97ad3ca02084))
|
|
15
|
+
|
|
16
|
+
- refactor(ml-config)!: change pretenders merge behavior ([e7b00ab](https://github.com/markuplint/markuplint/commit/e7b00abd80dd75a6060697b30d59d0371ae3694b))
|
|
17
|
+
- refactor(ml-config)!: change rule value array merge to override ([05c23ac](https://github.com/markuplint/markuplint/commit/05c23ace31a3429233b3411c8b95ae62438be6e5)), closes [#1104](https://github.com/markuplint/markuplint/issues/1104)
|
|
18
|
+
- refactor(ml-config)!: replace deepmerge with shallow merge ([15b4945](https://github.com/markuplint/markuplint/commit/15b494546b9016189a790b2ea49fcc2bb38c85c4))
|
|
19
|
+
|
|
20
|
+
### Features
|
|
21
|
+
|
|
22
|
+
- **ml-config:** add name and specConformance properties to nodeRule types ([af53042](https://github.com/markuplint/markuplint/commit/af5304218f7a207a1d8e61464c81c42d5ee1bf01))
|
|
23
|
+
- **ml-config:** add named rule group types, merge logic, and type guards ([9f625fd](https://github.com/markuplint/markuplint/commit/9f625fdcd9bc821d2be53668ac0eb676597aa935))
|
|
24
|
+
- **ml-config:** add ruleCommonSettings.ariaVersion option ([f2cd713](https://github.com/markuplint/markuplint/commit/f2cd7132311c00c22d68c4685b4a280b77ee6463))
|
|
9
25
|
|
|
26
|
+
### BREAKING CHANGES
|
|
10
27
|
|
|
28
|
+
- Pretender files/imports are now overridden instead
|
|
29
|
+
of deep-merged. Pretender data continues to be appended.
|
|
11
30
|
|
|
31
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
12
32
|
|
|
33
|
+
- Rule value arrays are now overridden instead of
|
|
34
|
+
- Object properties in config are now shallow-merged
|
|
35
|
+
instead of deep-merged. Nested objects within parser, specs, etc.
|
|
36
|
+
will be replaced entirely by the overriding config.
|
|
37
|
+
|
|
38
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
39
|
+
|
|
40
|
+
- **ml-config:** RuleV2, RuleConfigV2, AnyRuleV2 types are removed.
|
|
41
|
+
The deprecated `option` field is no longer supported; use `options`.
|
|
42
|
+
|
|
43
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
44
|
+
|
|
45
|
+
## [4.8.15](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.14...@markuplint/ml-config@4.8.15) (2026-02-10)
|
|
46
|
+
|
|
47
|
+
**Note:** Version bump only for package @markuplint/ml-config
|
|
48
|
+
|
|
49
|
+
## [4.8.14](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.13...@markuplint/ml-config@4.8.14) (2025-11-05)
|
|
50
|
+
|
|
51
|
+
**Note:** Version bump only for package @markuplint/ml-config
|
|
13
52
|
|
|
14
53
|
## [4.8.13](https://github.com/markuplint/markuplint/compare/@markuplint/ml-config@4.8.12...@markuplint/ml-config@4.8.13) (2025-08-24)
|
|
15
54
|
|
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 shallow 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 values override (right-side wins)
|
|
98
|
+
- Shallow 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,178 @@
|
|
|
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
|
+
- オブジェクト shallow 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)` -- 右辺優先の shallow 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()` が両方の入力を正規化
|
|
92
|
+
- `false` チェック: override が `false` または `{value: false}` なら常に `false` を返す
|
|
93
|
+
- `undefined` チェック: 片方がない場合はもう片方を返す
|
|
94
|
+
- 値型チェック: override が直接値(primitive/null/array)ならベース値を上書き
|
|
95
|
+
- オブジェクト型マージ: severity/value/reason は右辺優先、options は shallow merge
|
|
96
|
+
3. 変更を加える際、主要な不変条件を保持:
|
|
97
|
+
- `false` は常に絶対無効化になる必要がある
|
|
98
|
+
- 配列値は上書き(右辺優先)であり、連結ではない
|
|
99
|
+
- `options` は `mergeObject()` による shallow 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: [...]}` に変換(`toPretenderDetails()`)
|
|
111
|
+
- `files`/`imports` は上書き(右辺優先)
|
|
112
|
+
- `data` 配列は連結(追加)
|
|
113
|
+
- `PretenderDetails` の新しいフィールドは `mergePretenders()` 内で明示的に処理が必要
|
|
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()` は設計上、配列値を上書きする(右辺優先)。これは ESLint や Biome と一貫した動作。
|
|
153
|
+
|
|
154
|
+
**解決策:** これは期待される動作です。両方の値が必要な場合は、単一の設定で手動で配列を統合:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{ "value": ["base-tag-1", "base-tag-2", "new-tag-1"], "options": {} }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### プラグインの settings がマージされない
|
|
161
|
+
|
|
162
|
+
**症状:** 同名プラグインの2つの設定で、片方の settings しか反映されない。
|
|
163
|
+
|
|
164
|
+
**原因:** 片方が文字列形式(`"plugin-name"`)、もう片方がオブジェクト形式(`{name: "plugin-name", settings: {...}}`)の場合、文字列形式にはマージする settings がない。
|
|
165
|
+
|
|
166
|
+
**解決策:** 両方でオブジェクト形式を使用:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "name": "plugin-name", "settings": { "key": "value" } }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### false でルールを無効化できない
|
|
173
|
+
|
|
174
|
+
**症状:** ローカル設定でルールを `false` に設定しても、extends を使用していると無効化されない。
|
|
175
|
+
|
|
176
|
+
**原因:** `mergeConfig()` の呼び出し順序が逆になっている可能性がある。ローカル設定は第2引数(override)でなければならない。
|
|
177
|
+
|
|
178
|
+
**解決策:** 呼び出し順序が `mergeConfig(extendedConfig, localConfig)` であることを確認。ローカル設定が右辺(override)引数になるようにする。
|
|
@@ -0,0 +1,178 @@
|
|
|
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 shallow 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)` -- Shallow 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
|
|
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 the base value
|
|
95
|
+
- Object type merge: severity/value/reason use right-side precedence, options use shallow merge
|
|
96
|
+
3. Make changes, preserving the key invariants:
|
|
97
|
+
- `false` must always result in absolute disable
|
|
98
|
+
- Array values must override (right-side wins), not concatenate
|
|
99
|
+
- `options` must use shallow 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 `toPretenderDetails()`
|
|
111
|
+
- `files`/`imports` are overridden (right-side wins)
|
|
112
|
+
- `data` arrays are concatenated (appended)
|
|
113
|
+
- New fields on `PretenderDetails` need explicit handling in `mergePretenders()`
|
|
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 unexpectedly replaced
|
|
149
|
+
|
|
150
|
+
**Symptom:** A rule's array value is replaced entirely instead of keeping both base and override values.
|
|
151
|
+
|
|
152
|
+
**Cause:** `mergeRule()` overrides array values by design (right-side wins). This is consistent with ESLint and Biome behavior.
|
|
153
|
+
|
|
154
|
+
**Solution:** This is the expected behavior. If you need both sets of values, manually combine the arrays in a single config:
|
|
155
|
+
|
|
156
|
+
```json
|
|
157
|
+
{ "value": ["base-tag-1", "base-tag-2", "new-tag-1"], "options": {} }
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
### Plugin settings are not merged
|
|
161
|
+
|
|
162
|
+
**Symptom:** Two configs with the same plugin name result in settings from only one side.
|
|
163
|
+
|
|
164
|
+
**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.
|
|
165
|
+
|
|
166
|
+
**Solution:** Use the object form on both sides:
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "name": "plugin-name", "settings": { "key": "value" } }
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Rule cannot be disabled with false
|
|
173
|
+
|
|
174
|
+
**Symptom:** Setting a rule to `false` in a local config does not disable it when using extends.
|
|
175
|
+
|
|
176
|
+
**Cause:** The `mergeConfig()` call order may be reversed. The local config must be the second argument (override).
|
|
177
|
+
|
|
178
|
+
**Solution:** Ensure the call order is `mergeConfig(extendedConfig, localConfig)` where the local config is the right-side (override) argument.
|
package/lib/index.d.ts
CHANGED
package/lib/index.js
CHANGED
package/lib/merge-config.d.ts
CHANGED
|
@@ -1,4 +1,30 @@
|
|
|
1
|
-
import type { Config, AnyRule,
|
|
1
|
+
import type { Config, AnyRule, OptimizedConfig } from './types.js';
|
|
2
2
|
import type { Nullable } from '@markuplint/shared';
|
|
3
|
+
/**
|
|
4
|
+
* 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 (settings shallow-merged)
|
|
8
|
+
* - Arrays (excludeFiles, nodeRules, childNodeRules) are concatenated
|
|
9
|
+
* - Rules are merged per-key with right-side precedence
|
|
10
|
+
* - Objects (parser, specs, etc.) are shallow-merged
|
|
11
|
+
* - The `extends` property is removed from the result when `b` is provided
|
|
12
|
+
*
|
|
13
|
+
* @param a - The base configuration
|
|
14
|
+
* @param b - The configuration to merge on top of `a`
|
|
15
|
+
* @returns The merged and optimized configuration
|
|
16
|
+
*/
|
|
3
17
|
export declare function mergeConfig(a: Config, b?: Config): OptimizedConfig;
|
|
4
|
-
|
|
18
|
+
/**
|
|
19
|
+
* Merges two rule configurations with right-side precedence.
|
|
20
|
+
*
|
|
21
|
+
* If `b` is `false`, the rule is unconditionally disabled.
|
|
22
|
+
* If `b` is a direct value (including arrays), it overrides `a`.
|
|
23
|
+
* If both are full config objects, their properties are merged
|
|
24
|
+
* (severity/value/reason: right-side wins, options: shallow-merged).
|
|
25
|
+
*
|
|
26
|
+
* @param a - The base rule configuration (may be `null` or `undefined`)
|
|
27
|
+
* @param b - The rule configuration to merge on top
|
|
28
|
+
* @returns The merged rule configuration
|
|
29
|
+
*/
|
|
30
|
+
export declare function mergeRule(a: Nullable<AnyRule>, b: AnyRule): AnyRule;
|