@markuplint/ml-core 4.13.2 → 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 +524 -0
- package/ARCHITECTURE.md +524 -0
- package/CHANGELOG.md +52 -2
- package/README.md +5 -0
- package/SKILL.md +61 -0
- package/docs/linting-pipeline.ja.md +307 -0
- package/docs/linting-pipeline.md +307 -0
- package/docs/maintenance.ja.md +210 -0
- package/docs/maintenance.md +210 -0
- package/docs/ml-dom/attr.ja.md +103 -0
- package/docs/ml-dom/attr.md +103 -0
- package/docs/ml-dom/block.ja.md +272 -0
- package/docs/ml-dom/block.md +272 -0
- package/docs/ml-dom/document.ja.md +141 -0
- package/docs/ml-dom/document.md +141 -0
- package/docs/ml-dom/element.ja.md +176 -0
- package/docs/ml-dom/element.md +176 -0
- package/docs/ml-dom/helpers.ja.md +203 -0
- package/docs/ml-dom/helpers.md +203 -0
- package/docs/ml-dom/node.ja.md +199 -0
- package/docs/ml-dom/node.md +199 -0
- package/docs/ml-dom/others.ja.md +120 -0
- package/docs/ml-dom/others.md +120 -0
- package/docs/ml-dom/overview.ja.md +102 -0
- package/docs/ml-dom/overview.md +102 -0
- package/docs/ml-dom/pretender.ja.md +269 -0
- package/docs/ml-dom/pretender.md +269 -0
- package/docs/ml-dom/rule-mapping.ja.md +371 -0
- package/docs/ml-dom/rule-mapping.md +371 -0
- package/docs/ml-dom.ja.md +18 -0
- package/docs/ml-dom.md +18 -0
- package/docs/rule-system.ja.md +287 -0
- package/docs/rule-system.md +287 -0
- package/lib/convert-ruleset.d.ts +7 -0
- package/lib/convert-ruleset.js +7 -0
- package/lib/debug.d.ts +4 -0
- package/lib/debug.js +4 -0
- package/lib/index.d.ts +4 -3
- package/lib/index.js +1 -1
- package/lib/ml-core.d.ts +37 -1
- package/lib/ml-core.js +171 -82
- package/lib/ml-dom/helper/accname.d.ts +8 -0
- package/lib/ml-dom/helper/accname.js +71 -55
- package/lib/ml-dom/helper/create-node.js +1 -0
- package/lib/ml-dom/helper/get-indent.d.ts +4 -1
- package/lib/ml-dom/helper/get-indent.js +21 -30
- package/lib/ml-dom/node/attr.d.ts +65 -4
- package/lib/ml-dom/node/attr.js +151 -53
- package/lib/ml-dom/node/block.d.ts +23 -2
- package/lib/ml-dom/node/block.js +24 -1
- package/lib/ml-dom/node/child-node.d.ts +9 -0
- package/lib/ml-dom/node/child-node.js +9 -0
- package/lib/ml-dom/node/comment.d.ts +7 -0
- package/lib/ml-dom/node/comment.js +7 -0
- package/lib/ml-dom/node/document-fragment.d.ts +8 -0
- package/lib/ml-dom/node/document-fragment.js +8 -0
- package/lib/ml-dom/node/document-type.d.ts +22 -0
- package/lib/ml-dom/node/document-type.js +25 -0
- package/lib/ml-dom/node/document.d.ts +88 -7
- package/lib/ml-dom/node/document.js +128 -32
- package/lib/ml-dom/node/dom-token-list.js +17 -30
- package/lib/ml-dom/node/element-close-tag.js +1 -0
- package/lib/ml-dom/node/element.d.ts +151 -5
- package/lib/ml-dom/node/element.js +242 -50
- package/lib/ml-dom/node/node-store.js +6 -15
- package/lib/ml-dom/node/node.d.ts +19 -1
- package/lib/ml-dom/node/node.js +175 -166
- package/lib/ml-dom/node/parent-node.js +14 -30
- package/lib/ml-dom/node/rule-mapper.js +7 -20
- package/lib/ml-dom/node/text.d.ts +19 -0
- package/lib/ml-dom/node/text.js +21 -0
- package/lib/ml-dom/node/types.d.ts +68 -0
- package/lib/ml-dom/token/token.d.ts +42 -0
- package/lib/ml-dom/token/token.js +59 -39
- package/lib/ml-rule/create-rule.d.ts +17 -1
- package/lib/ml-rule/ml-rule-context.js +7 -11
- package/lib/ml-rule/ml-rule.d.ts +66 -1
- package/lib/ml-rule/ml-rule.js +95 -25
- package/lib/ml-rule/types.d.ts +41 -0
- package/lib/plugin/plugin.d.ts +8 -0
- package/lib/plugin/plugin.js +8 -0
- package/lib/plugin/types.d.ts +21 -0
- package/lib/ruleset/index.d.ts +10 -0
- package/lib/ruleset/index.js +13 -0
- package/lib/test/index.d.ts +42 -1
- package/lib/test/index.js +39 -2
- package/lib/types.d.ts +10 -1
- package/lib/violation-collector.d.ts +33 -0
- package/lib/violation-collector.js +48 -28
- package/lib/virtual-rule.d.ts +72 -0
- package/lib/virtual-rule.js +233 -0
- package/package.json +16 -13
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# メンテナンスガイド
|
|
2
|
+
|
|
3
|
+
## コマンド
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# ビルド
|
|
7
|
+
yarn build --scope @markuplint/ml-core
|
|
8
|
+
|
|
9
|
+
# ウォッチモード
|
|
10
|
+
yarn dev --scope @markuplint/ml-core
|
|
11
|
+
|
|
12
|
+
# ビルド出力のクリーン
|
|
13
|
+
yarn clean --scope @markuplint/ml-core
|
|
14
|
+
|
|
15
|
+
# テスト(リポジトリルートから)
|
|
16
|
+
yarn test --scope @markuplint/ml-core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## テスト
|
|
20
|
+
|
|
21
|
+
### テストファイル
|
|
22
|
+
|
|
23
|
+
| ファイル | 用途 |
|
|
24
|
+
| ------------------- | ---------------------------------------------------------------------------------- |
|
|
25
|
+
| `src/test/index.ts` | テストユーティリティエクスポート(`createTestDocument`, `createTestElement` など) |
|
|
26
|
+
|
|
27
|
+
### テストユーティリティの使用
|
|
28
|
+
|
|
29
|
+
パッケージは `@markuplint/rules` や他のコンシューマーで一般的に使用されるテストヘルパーを提供します:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createTestDocument, createTestElement, dummySchemas } from '@markuplint/ml-core';
|
|
33
|
+
|
|
34
|
+
// HTML をテスト用 MLDocument にパース
|
|
35
|
+
const doc = createTestDocument('<div class="foo"><p>Hello</p></div>');
|
|
36
|
+
|
|
37
|
+
// ノードリストにアクセス
|
|
38
|
+
for (const node of doc.nodeList) {
|
|
39
|
+
console.log(node.nodeName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 最初の要素を直接取得
|
|
43
|
+
const el = createTestElement('<button type="submit">Click</button>');
|
|
44
|
+
console.log(el.localName); // 'button'
|
|
45
|
+
console.log(el.getAttribute('type')); // 'submit'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### カスタム設定でのテスト
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const doc = createTestDocument('<div></div>', {
|
|
52
|
+
config: {
|
|
53
|
+
rules: {
|
|
54
|
+
'my-rule': true,
|
|
55
|
+
},
|
|
56
|
+
nodeRules: [{ selector: 'div', rules: { 'my-rule': 'custom-value' } }],
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### カスタムパーサーでのテスト
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { parser as vueParser } from '@markuplint/vue-parser';
|
|
65
|
+
|
|
66
|
+
const doc = createTestDocument('<template><div></div></template>', {
|
|
67
|
+
parser: vueParser,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## レシピ
|
|
72
|
+
|
|
73
|
+
### 1. MLDOM ノードクラスへのプロパティ追加
|
|
74
|
+
|
|
75
|
+
1. `src/ml-dom/node/` 内の対象クラスを特定(例:`MLElement` の場合 `element.ts`)
|
|
76
|
+
2. ゲッターまたは readonly フィールドとしてプロパティを追加
|
|
77
|
+
3. プロパティが AST データから派生する場合、`this.#astNode`(プライベート AST 参照)を使用
|
|
78
|
+
4. プロパティが仕様データを必要とする場合、`this.ownerMLDocument.specs` 経由でアクセス
|
|
79
|
+
5. 新しい型を導入する場合、`src/ml-dom/node/types.ts` の型定義を更新
|
|
80
|
+
6. ビルドを検証:`yarn build --scope @markuplint/ml-core`
|
|
81
|
+
|
|
82
|
+
**例:MLElement に `hasId` プロパティを追加**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// src/ml-dom/node/element.ts 内
|
|
86
|
+
get hasId(): boolean {
|
|
87
|
+
return this.hasAttribute('id');
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. DOM API 更新への対応(TypeScript DOM 型定義の変更)
|
|
92
|
+
|
|
93
|
+
TypeScript 組み込みの DOM 型定義が更新された場合(例:`Element` や `Node` に新しいプロパティが追加された場合)、それらのインターフェースを `implements` している MLDOM クラスで型エラーが発生します。これは意図的な設計です -- 気づかないうちにギャップが生じることを防ぎます。
|
|
94
|
+
|
|
95
|
+
**プロセス:**
|
|
96
|
+
|
|
97
|
+
1. `yarn build --scope @markuplint/ml-core` を実行し、型エラーを収集する
|
|
98
|
+
2. 不足しているプロパティやメソッドごとに判断する:
|
|
99
|
+
- **実装する**: API がリントルールに有用な場合(例:`querySelector`、`getAttribute`)→ 実際のロジックを実装する
|
|
100
|
+
- **非サポートにする**: API が静的解析で意味を持たない場合(例:`requestFullscreen`、`animate`)→ `UnexpectedCallError` をスローするスタブを追加する
|
|
101
|
+
3. 非サポートのスタブは既存のパターンに従う:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
/**
|
|
105
|
+
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
106
|
+
*
|
|
107
|
+
* @unsupported
|
|
108
|
+
* @implements DOM API: `Element`
|
|
109
|
+
*/
|
|
110
|
+
someNewMethod(): void {
|
|
111
|
+
throw new UnexpectedCallError('Not supported "someNewMethod" method');
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
4. ビルドが通ることを確認する:`yarn build --scope @markuplint/ml-core`
|
|
116
|
+
|
|
117
|
+
### 3. 新しいリンティングルールの作成
|
|
118
|
+
|
|
119
|
+
ルールは `@markuplint/rules` に配置されますが、このパッケージの `createRule` API を使用します:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createRule } from '@markuplint/ml-core';
|
|
123
|
+
|
|
124
|
+
export default createRule({
|
|
125
|
+
defaultSeverity: 'error',
|
|
126
|
+
defaultValue: true,
|
|
127
|
+
async verify({ document, report, t }) {
|
|
128
|
+
await document.walkOn('Element', el => {
|
|
129
|
+
if (/* 違反条件 */) {
|
|
130
|
+
report({
|
|
131
|
+
scope: el,
|
|
132
|
+
message: t('違反メッセージ'),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
ルールを単体テストする場合、`src/ml-rule/create-test-rule.ts` の `createTestRule` を使用:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { createRule as createTestRule } from '@markuplint/ml-core/test';
|
|
144
|
+
const rule = createTestRule({ name: 'my-rule', ...seed });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 4. Pretender 設定の変更
|
|
148
|
+
|
|
149
|
+
pretender は `MLDocument` コンストラクタ(`src/ml-dom/node/document.ts`)で処理されます:
|
|
150
|
+
|
|
151
|
+
1. pretender 定義は `MLFabric.pretenders` から取得
|
|
152
|
+
2. ドキュメント構築時に、各要素が pretender セレクタに対してチェックされる
|
|
153
|
+
3. マッチする要素に `pretenderContext` が割り当てられる
|
|
154
|
+
4. pretender の動作を変更するには、`MLDocument` の pretender 初期化ロジックを修正
|
|
155
|
+
5. `createTestDocument` の `pretenders` オプションでテスト
|
|
156
|
+
|
|
157
|
+
### 5. ルールマッピングロジックの変更(RuleMapper)
|
|
158
|
+
|
|
159
|
+
`RuleMapper` クラス(`src/ml-dom/node/rule-mapper.ts`)はルールのノードへの適用方法を制御します:
|
|
160
|
+
|
|
161
|
+
1. `apply()` はグローバルルール、nodeRules、childNodeRules を順に処理
|
|
162
|
+
2. マッチするセレクタごとに、詳細度付きの `MappingLayer` を作成
|
|
163
|
+
3. `set()` はレイヤーを対象ノードに割り当て、詳細度で競合を解決
|
|
164
|
+
4. 新しいマッピングソースを追加するには、`apply()` に新しい反復ブロックを追加し、適切な `from` 値を定義
|
|
165
|
+
|
|
166
|
+
### 6. walkOn に新しいノードタイプを追加
|
|
167
|
+
|
|
168
|
+
`walkOn()` は `MLDocument`(`src/ml-dom/node/document.ts`)で定義されています:
|
|
169
|
+
|
|
170
|
+
1. `type` パラメータのユニオンに新しいケースを追加:`'Element' | 'Text' | 'Comment' | 'Attr' | 'ElementCloseTag' | 'NewType'`
|
|
171
|
+
2. `walkOn()` メソッドに `nodeList` からマッチするノードを選択するフィルタリングロジックを追加
|
|
172
|
+
3. 必要に応じて `src/ml-dom/helper/walkers.ts` の `Walker` 型を更新
|
|
173
|
+
4. `walkOn()` を使用する `@markuplint/rules` の下流コードを更新
|
|
174
|
+
|
|
175
|
+
## 下流影響チェックリスト
|
|
176
|
+
|
|
177
|
+
`@markuplint/ml-core` の変更は以下に影響を与える可能性があります:
|
|
178
|
+
|
|
179
|
+
- [ ] **`@markuplint/rules`** — すべての組み込みルールが MLDOM クラスと `createRule` に依存
|
|
180
|
+
- ノードのプロパティ/メソッドの変更はそれらにアクセスするルールに影響
|
|
181
|
+
- `walkOn()` や `MLRuleContext` の変更はすべてのルールに影響
|
|
182
|
+
- `RuleMapper` の変更はルール設定の解決に影響
|
|
183
|
+
- [ ] **`markuplint`** — CLI と API が `MLCore`, `ViolationCollector`, `convertRuleset` に依存
|
|
184
|
+
- `MLCore.verify()` のシグネチャや動作の変更は `MLEngine` に影響
|
|
185
|
+
- `MLFabric` 型の変更はエンジンの初期化に影響
|
|
186
|
+
|
|
187
|
+
## トラブルシューティング
|
|
188
|
+
|
|
189
|
+
### ルールが実行されない
|
|
190
|
+
|
|
191
|
+
1. ルールがルールセット設定(`rules` フィールド)に登録されているか確認
|
|
192
|
+
2. ルール名が完全に一致しているか確認(大文字小文字を区別)
|
|
193
|
+
3. `RuleMapper` の出力を確認:`document.debugMap()` でルールマッピングを確認
|
|
194
|
+
4. ルールの `walkOn` タイプが期待するノードタイプと一致しているか確認
|
|
195
|
+
5. nodeRules/childNodeRules のセレクタがグローバルルールを無効にオーバーライドしていないか確認
|
|
196
|
+
|
|
197
|
+
### Pretender が効かない
|
|
198
|
+
|
|
199
|
+
1. pretender セレクタが対象要素にマッチするか確認:`element.matches(selector)` でテスト
|
|
200
|
+
2. pretender 定義が `MLFabric.pretenders` を通じて渡されているか確認
|
|
201
|
+
3. pretender の `as` 値が有効な HTML 要素名であることを確認
|
|
202
|
+
4. ドキュメント構築後に `element.pretenderContext` が `null` でないことを確認
|
|
203
|
+
|
|
204
|
+
### DOM ツリー構造が不正
|
|
205
|
+
|
|
206
|
+
1. `document.debugMap()` の出力を期待される構造と比較
|
|
207
|
+
2. パーサー出力を確認:同じソースをパーサーで直接パースして AST を検査
|
|
208
|
+
3. ゴースト要素(暗黙の HTML/head/body)が正しく処理されているか確認
|
|
209
|
+
4. テンプレートエンジンの場合、`blockBehavior` 付きの `MLBlock` ノードが期待される子をラップしているか確認
|
|
210
|
+
5. `document.nodeList` でフラットノードリストを検査し、親子関係を確認
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
# Maintenance Guide
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
```bash
|
|
6
|
+
# Build
|
|
7
|
+
yarn build --scope @markuplint/ml-core
|
|
8
|
+
|
|
9
|
+
# Watch mode
|
|
10
|
+
yarn dev --scope @markuplint/ml-core
|
|
11
|
+
|
|
12
|
+
# Clean build output
|
|
13
|
+
yarn clean --scope @markuplint/ml-core
|
|
14
|
+
|
|
15
|
+
# Test (from repo root)
|
|
16
|
+
yarn test --scope @markuplint/ml-core
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Testing
|
|
20
|
+
|
|
21
|
+
### Test Files
|
|
22
|
+
|
|
23
|
+
| File | Purpose |
|
|
24
|
+
| ------------------- | ---------------------------------------------------------------------- |
|
|
25
|
+
| `src/test/index.ts` | Test utility exports (`createTestDocument`, `createTestElement`, etc.) |
|
|
26
|
+
|
|
27
|
+
### Using Test Utilities
|
|
28
|
+
|
|
29
|
+
The package provides test helpers commonly used by `@markuplint/rules` and other consumers:
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
import { createTestDocument, createTestElement, dummySchemas } from '@markuplint/ml-core';
|
|
33
|
+
|
|
34
|
+
// Parse HTML into an MLDocument for testing
|
|
35
|
+
const doc = createTestDocument('<div class="foo"><p>Hello</p></div>');
|
|
36
|
+
|
|
37
|
+
// Access the node list
|
|
38
|
+
for (const node of doc.nodeList) {
|
|
39
|
+
console.log(node.nodeName);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Get the first element directly
|
|
43
|
+
const el = createTestElement('<button type="submit">Click</button>');
|
|
44
|
+
console.log(el.localName); // 'button'
|
|
45
|
+
console.log(el.getAttribute('type')); // 'submit'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Test with Custom Config
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
const doc = createTestDocument('<div></div>', {
|
|
52
|
+
config: {
|
|
53
|
+
rules: {
|
|
54
|
+
'my-rule': true,
|
|
55
|
+
},
|
|
56
|
+
nodeRules: [{ selector: 'div', rules: { 'my-rule': 'custom-value' } }],
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Test with Custom Parser
|
|
62
|
+
|
|
63
|
+
```typescript
|
|
64
|
+
import { parser as vueParser } from '@markuplint/vue-parser';
|
|
65
|
+
|
|
66
|
+
const doc = createTestDocument('<template><div></div></template>', {
|
|
67
|
+
parser: vueParser,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Recipes
|
|
72
|
+
|
|
73
|
+
### 1. Add a Property to an MLDOM Node Class
|
|
74
|
+
|
|
75
|
+
1. Identify the target class in `src/ml-dom/node/` (e.g., `element.ts` for `MLElement`)
|
|
76
|
+
2. Add the property as a getter or readonly field
|
|
77
|
+
3. If the property derives from AST data, use `this.#astNode` (private AST reference)
|
|
78
|
+
4. If the property needs spec data, access it via `this.ownerMLDocument.specs`
|
|
79
|
+
5. Update the type definitions in `src/ml-dom/node/types.ts` if introducing a new type
|
|
80
|
+
6. Verify the build: `yarn build --scope @markuplint/ml-core`
|
|
81
|
+
|
|
82
|
+
**Example: Adding a `hasId` property to MLElement**
|
|
83
|
+
|
|
84
|
+
```typescript
|
|
85
|
+
// In src/ml-dom/node/element.ts
|
|
86
|
+
get hasId(): boolean {
|
|
87
|
+
return this.hasAttribute('id');
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### 2. Handle DOM API Updates (TypeScript DOM Type Changes)
|
|
92
|
+
|
|
93
|
+
When the TypeScript built-in DOM type definitions are updated (e.g., a new property is added to `Element` or `Node`), MLDOM classes that `implements` those interfaces will produce type errors. This is intentional -- it ensures no gaps go unnoticed.
|
|
94
|
+
|
|
95
|
+
**Process:**
|
|
96
|
+
|
|
97
|
+
1. Run `yarn build --scope @markuplint/ml-core` and collect the type errors
|
|
98
|
+
2. For each missing property or method, decide:
|
|
99
|
+
- **Implement**: If the API is useful for lint rules (e.g., `querySelector`, `getAttribute`) → implement the actual logic
|
|
100
|
+
- **Mark as unsupported**: If the API is not meaningful in static analysis (e.g., `requestFullscreen`, `animate`) → add a stub that throws `UnexpectedCallError`
|
|
101
|
+
3. For unsupported stubs, follow the existing pattern:
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
/**
|
|
105
|
+
* **IT THROWS AN ERROR WHEN CALLING THIS.**
|
|
106
|
+
*
|
|
107
|
+
* @unsupported
|
|
108
|
+
* @implements DOM API: `Element`
|
|
109
|
+
*/
|
|
110
|
+
someNewMethod(): void {
|
|
111
|
+
throw new UnexpectedCallError('Not supported "someNewMethod" method');
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
4. Verify the build passes: `yarn build --scope @markuplint/ml-core`
|
|
116
|
+
|
|
117
|
+
### 3. Create a New Linting Rule
|
|
118
|
+
|
|
119
|
+
Rules live in `@markuplint/rules`, but use the `createRule` API from this package:
|
|
120
|
+
|
|
121
|
+
```typescript
|
|
122
|
+
import { createRule } from '@markuplint/ml-core';
|
|
123
|
+
|
|
124
|
+
export default createRule({
|
|
125
|
+
defaultSeverity: 'error',
|
|
126
|
+
defaultValue: true,
|
|
127
|
+
async verify({ document, report, t }) {
|
|
128
|
+
await document.walkOn('Element', el => {
|
|
129
|
+
if (/* violation condition */) {
|
|
130
|
+
report({
|
|
131
|
+
scope: el,
|
|
132
|
+
message: t('Violation message'),
|
|
133
|
+
});
|
|
134
|
+
}
|
|
135
|
+
});
|
|
136
|
+
},
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
For testing rules in isolation, use `createTestRule` from `src/ml-rule/create-test-rule.ts`:
|
|
141
|
+
|
|
142
|
+
```typescript
|
|
143
|
+
import { createRule as createTestRule } from '@markuplint/ml-core/test';
|
|
144
|
+
const rule = createTestRule({ name: 'my-rule', ...seed });
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 4. Modify Pretender Configuration
|
|
148
|
+
|
|
149
|
+
Pretenders are processed in `MLDocument` constructor (`src/ml-dom/node/document.ts`):
|
|
150
|
+
|
|
151
|
+
1. Pretender definitions come from `MLFabric.pretenders`
|
|
152
|
+
2. During document construction, each element is checked against pretender selectors
|
|
153
|
+
3. Matching elements get a `pretenderContext` assigned
|
|
154
|
+
4. To change pretender behavior, modify the pretender initialization logic in `MLDocument`
|
|
155
|
+
5. Test with `createTestDocument` using the `pretenders` option
|
|
156
|
+
|
|
157
|
+
### 5. Change Rule Mapping Logic (RuleMapper)
|
|
158
|
+
|
|
159
|
+
The `RuleMapper` class (`src/ml-dom/node/rule-mapper.ts`) controls how rules are applied to nodes:
|
|
160
|
+
|
|
161
|
+
1. `apply()` iterates through global rules, nodeRules, and childNodeRules
|
|
162
|
+
2. For each matching selector, it creates a `MappingLayer` with specificity
|
|
163
|
+
3. `set()` assigns the layer to the target node, resolving conflicts by specificity
|
|
164
|
+
4. To add a new mapping source, add a new iteration block in `apply()` and define the appropriate `from` value
|
|
165
|
+
|
|
166
|
+
### 6. Add a New Node Type to walkOn
|
|
167
|
+
|
|
168
|
+
`walkOn()` is defined in `MLDocument` (`src/ml-dom/node/document.ts`):
|
|
169
|
+
|
|
170
|
+
1. Add a new case to the `type` parameter union: `'Element' | 'Text' | 'Comment' | 'Attr' | 'ElementCloseTag' | 'NewType'`
|
|
171
|
+
2. Add the filtering logic in the `walkOn()` method to select matching nodes from `nodeList`
|
|
172
|
+
3. Update the `Walker` type in `src/ml-dom/helper/walkers.ts` if needed
|
|
173
|
+
4. Update downstream code in `@markuplint/rules` that uses `walkOn()`
|
|
174
|
+
|
|
175
|
+
## Downstream Impact Checklist
|
|
176
|
+
|
|
177
|
+
Changes to `@markuplint/ml-core` can affect:
|
|
178
|
+
|
|
179
|
+
- [ ] **`@markuplint/rules`** — All built-in rules depend on MLDOM classes and `createRule`
|
|
180
|
+
- Changes to node properties/methods affect rules that access them
|
|
181
|
+
- Changes to `walkOn()` or `MLRuleContext` affect all rules
|
|
182
|
+
- Changes to `RuleMapper` affect rule configuration resolution
|
|
183
|
+
- [ ] **`markuplint`** — CLI and API depend on `MLCore`, `ViolationCollector`, `convertRuleset`
|
|
184
|
+
- Changes to `MLCore.verify()` signature or behavior affect `MLEngine`
|
|
185
|
+
- Changes to `MLFabric` type affect engine initialization
|
|
186
|
+
|
|
187
|
+
## Troubleshooting
|
|
188
|
+
|
|
189
|
+
### Rule is not being executed
|
|
190
|
+
|
|
191
|
+
1. Check if the rule is registered in the ruleset config (`rules` field)
|
|
192
|
+
2. Verify the rule name matches exactly (case-sensitive)
|
|
193
|
+
3. Check `RuleMapper` output: use `document.debugMap()` to see the rule mapping
|
|
194
|
+
4. Verify the rule's `walkOn` type matches the node type you expect
|
|
195
|
+
5. Check if nodeRules/childNodeRules selectors are overriding the global rule to disable it
|
|
196
|
+
|
|
197
|
+
### Pretender is not taking effect
|
|
198
|
+
|
|
199
|
+
1. Verify the pretender selector matches the target element: use `element.matches(selector)` to test
|
|
200
|
+
2. Check that pretender definitions are passed through `MLFabric.pretenders`
|
|
201
|
+
3. Ensure the pretender `as` value is a valid HTML element name
|
|
202
|
+
4. Check `element.pretenderContext` is not `null` after document construction
|
|
203
|
+
|
|
204
|
+
### DOM tree structure is incorrect
|
|
205
|
+
|
|
206
|
+
1. Compare `document.debugMap()` output with expected structure
|
|
207
|
+
2. Check the parser output: parse the same source with the parser directly and inspect the AST
|
|
208
|
+
3. Verify ghost elements (implicit HTML/head/body) are handled correctly
|
|
209
|
+
4. For template engines, check that `MLBlock` nodes with `blockBehavior` wrap the expected children
|
|
210
|
+
5. Use `document.nodeList` to inspect the flat node list and verify parent-child relationships
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# MLAttr
|
|
2
|
+
|
|
3
|
+
**ソース:** `src/ml-dom/node/attr.ts`
|
|
4
|
+
|
|
5
|
+
属性ノードです。`MLNode` を継承し、DOM `Attr` インターフェースを実装します。各属性は細粒度の検査と修正のために個々のトークンに分解されます。
|
|
6
|
+
|
|
7
|
+
## プロパティ
|
|
8
|
+
|
|
9
|
+
| プロパティ | 型 | 説明 |
|
|
10
|
+
| -------------- | ---------------- | -------------------------------------------------- |
|
|
11
|
+
| `name` | `string` | 修飾された属性名(`#potentialName` から解決) |
|
|
12
|
+
| `localName` | `string` | 属性名のローカル部分(名前空間プレフィックスなし) |
|
|
13
|
+
| `namespaceURI` | `string \| null` | 属性の名前空間 URI |
|
|
14
|
+
| `value` | `string` | 属性値(`#potentialValue` から解決) |
|
|
15
|
+
| `nodeValue` | `string` | `value` と同じ |
|
|
16
|
+
| `textContent` | `string` | `value` と同じ |
|
|
17
|
+
| `specified` | `true` | 常に `true`(DOM `Attr` インターフェース要件) |
|
|
18
|
+
| `ownerElement` | `MLElement` | この属性を所有する要素 |
|
|
19
|
+
|
|
20
|
+
## `#potentialName` / `#potentialValue`
|
|
21
|
+
|
|
22
|
+
パーサーは、ソース構文が有効なセマンティクスと異なる属性に対して、解決済みの「potential」名と値を提供する場合があります。例えば、テンプレートエンジンが属性名や値を変換することがあります。
|
|
23
|
+
|
|
24
|
+
- `astToken.potentialName` が存在する場合 → `name` として使用する。そうでなければ `nameNode.raw` を使用する
|
|
25
|
+
- `astToken.potentialValue` が存在する場合 → `value` として使用する。そうでなければ `valueNode.raw` を使用する
|
|
26
|
+
|
|
27
|
+
### ディレクティブパターン解決
|
|
28
|
+
|
|
29
|
+
パーサーが `astToken.potentialName` を設定していない場合、`MLAttr` コンストラクタは spec(例: `@markuplint/vue-spec`、`@markuplint/svelte-spec`)からの `directivePatterns` をチェックします。パターンが存在する場合、生の属性名とコンパイル済みパターンで `resolveDirective()` が呼び出されます。最初にマッチしたパターンが `potentialName`、`isDynamicValue`、`isDirective`、`isDuplicatable` を決定します。
|
|
30
|
+
|
|
31
|
+
### IDL 属性名解決
|
|
32
|
+
|
|
33
|
+
ディレクティブパターン解決の後、spec が `useIDLAttributeNames: true` を設定しており(例: `@markuplint/react-spec`、`@markuplint/svelte-spec`)、属性がディレクティブでない場合、コンストラクタは `@markuplint/parser-utils` の `searchIDLAttribute()` を呼び出して IDL プロパティ名を HTML コンテンツ属性名にマッピングします(例: `className` → `class`、`htmlFor` → `for`)。これはパーサーレベルではなくコアレベルの関心事です。
|
|
34
|
+
|
|
35
|
+
## トークン分解
|
|
36
|
+
|
|
37
|
+
各属性は個々の `MLToken` インスタンスに分解されます:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
·class="container"
|
|
41
|
+
^ ^^ ^
|
|
42
|
+
| || └── endQuote (raw: '"')
|
|
43
|
+
| |└── valueNode (raw: 'container')
|
|
44
|
+
| └── startQuote (raw: '"')
|
|
45
|
+
| equal (raw: '=')
|
|
46
|
+
└── spacesBeforeName (raw: ' ')
|
|
47
|
+
nameNode (raw: 'class')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| トークンプロパティ | 型 | 説明 |
|
|
51
|
+
| ------------------- | ----------------- | --------------------------------------------- |
|
|
52
|
+
| `spacesBeforeName` | `MLToken \| null` | 属性名の前の空白 |
|
|
53
|
+
| `nameNode` | `MLToken \| null` | 属性名トークン(スプレッド属性の場合は null) |
|
|
54
|
+
| `spacesBeforeEqual` | `MLToken \| null` | 名前と `=` の間の空白 |
|
|
55
|
+
| `equal` | `MLToken \| null` | `=` 記号トークン |
|
|
56
|
+
| `spacesAfterEqual` | `MLToken \| null` | `=` と値の間の空白 |
|
|
57
|
+
| `startQuote` | `MLToken \| null` | 開始引用符トークン |
|
|
58
|
+
| `valueNode` | `MLToken \| null` | 属性値トークン |
|
|
59
|
+
| `endQuote` | `MLToken \| null` | 終了引用符トークン |
|
|
60
|
+
|
|
61
|
+
## スプレッド属性
|
|
62
|
+
|
|
63
|
+
スプレッド属性(例:JSX の `{...props}`)の場合、`MLAttr` は最小限のプロパティで作成されます:
|
|
64
|
+
|
|
65
|
+
| プロパティ | 値 |
|
|
66
|
+
| -------------------------- | ---------------------------- |
|
|
67
|
+
| `localName` | `'#spread'` |
|
|
68
|
+
| `valueType` | `'code'` |
|
|
69
|
+
| `isDirective` | `true` |
|
|
70
|
+
| `isDynamicValue` | `true` |
|
|
71
|
+
| `isDuplicatable` | `true` |
|
|
72
|
+
| すべてのトークンプロパティ | `null` |
|
|
73
|
+
| `fix()` | No-op(即座に return) |
|
|
74
|
+
| `value` | スプレッド式のソーステキスト |
|
|
75
|
+
|
|
76
|
+
## `tokenList`(MLDomTokenList)
|
|
77
|
+
|
|
78
|
+
スペース区切りの属性値(例:`class` 属性)に対する `MLDomTokenList` を返します:
|
|
79
|
+
|
|
80
|
+
- `isDynamicValue` の場合 → `null` を返す(動的式はトークン化できない)
|
|
81
|
+
- それ以外の場合 → `new MLDomTokenList(this.value, [this])`
|
|
82
|
+
|
|
83
|
+
## `rule` ゲッター
|
|
84
|
+
|
|
85
|
+
`ownerElement.rule` に委譲します -- 属性はそれを所有する要素からルール設定を継承します。
|
|
86
|
+
|
|
87
|
+
## メタデータプロパティ
|
|
88
|
+
|
|
89
|
+
| プロパティ | 型 | 説明 |
|
|
90
|
+
| ---------------- | --------------------------------------------- | -------------------------------------------- |
|
|
91
|
+
| `isDynamicValue` | `true \| undefined` | 値に動的式が含まれているかどうか |
|
|
92
|
+
| `isDirective` | `true \| undefined` | 属性がフレームワークのディレクティブかどうか |
|
|
93
|
+
| `isDuplicatable` | `boolean` | 属性が複数回出現可能かどうか |
|
|
94
|
+
| `valueType` | `'string' \| 'number' \| 'boolean' \| 'code'` | 値の型分類 |
|
|
95
|
+
| `candidate` | `string \| undefined` | パーサーが提案する修正候補値 |
|
|
96
|
+
|
|
97
|
+
## メソッド
|
|
98
|
+
|
|
99
|
+
| メソッド | シグネチャ | 説明 |
|
|
100
|
+
| ------------------- | ----------------------------------- | ------------------------------------------------------------------------------------ |
|
|
101
|
+
| `fix` | `fix(raw: string): void` | `valueNode` の修正済みコンテンツを更新する。スプレッド属性の場合は No-op |
|
|
102
|
+
| `toNormalizeString` | `toNormalizeString(): string` | 余分な空白を除去した正規化された表現 |
|
|
103
|
+
| `toString` | `toString(fixed?: boolean): string` | raw または fixed の文字列。`fixed=true` の場合、各トークンの修正済み値から再構築する |
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
# MLAttr
|
|
2
|
+
|
|
3
|
+
**Source:** `src/ml-dom/node/attr.ts`
|
|
4
|
+
|
|
5
|
+
Attribute node. Extends `MLNode` and implements the DOM `Attr` interface. Each attribute is decomposed into individual tokens for fine-grained inspection and fixing.
|
|
6
|
+
|
|
7
|
+
## Properties
|
|
8
|
+
|
|
9
|
+
| Property | Type | Description |
|
|
10
|
+
| -------------- | ---------------- | --------------------------------------------------------- |
|
|
11
|
+
| `name` | `string` | Qualified attribute name (resolved from `#potentialName`) |
|
|
12
|
+
| `localName` | `string` | Local part of attribute name (without namespace prefix) |
|
|
13
|
+
| `namespaceURI` | `string \| null` | Attribute namespace URI |
|
|
14
|
+
| `value` | `string` | Attribute value (resolved from `#potentialValue`) |
|
|
15
|
+
| `nodeValue` | `string` | Same as `value` |
|
|
16
|
+
| `textContent` | `string` | Same as `value` |
|
|
17
|
+
| `specified` | `true` | Always `true` (DOM `Attr` interface requirement) |
|
|
18
|
+
| `ownerElement` | `MLElement` | Element that owns this attribute |
|
|
19
|
+
|
|
20
|
+
## `#potentialName` / `#potentialValue`
|
|
21
|
+
|
|
22
|
+
The parser may provide resolved "potential" names and values for attributes where the source syntax differs from the effective semantics. For example, a template engine might transform attribute names or values.
|
|
23
|
+
|
|
24
|
+
- If `astToken.potentialName` exists → use it as `name`; otherwise use `nameNode.raw`
|
|
25
|
+
- If `astToken.potentialValue` exists → use it as `value`; otherwise use `valueNode.raw`
|
|
26
|
+
|
|
27
|
+
### Directive Pattern Resolution
|
|
28
|
+
|
|
29
|
+
When `astToken.potentialName` is not set by the parser, the `MLAttr` constructor checks for `directivePatterns` from the spec (e.g., `@markuplint/vue-spec`, `@markuplint/svelte-spec`). If patterns exist, `resolveDirective()` is called with the raw attribute name and the compiled patterns. The first matching pattern determines `potentialName`, `isDynamicValue`, `isDirective`, and `isDuplicatable`.
|
|
30
|
+
|
|
31
|
+
### IDL Attribute Name Resolution
|
|
32
|
+
|
|
33
|
+
After directive pattern resolution, if the spec sets `useIDLAttributeNames: true` (e.g., `@markuplint/react-spec`, `@markuplint/svelte-spec`) and the attribute is not a directive, the constructor calls `searchIDLAttribute()` from `@markuplint/parser-utils` to map IDL property names to HTML content attribute names (e.g., `className` -> `class`, `htmlFor` -> `for`). This is a core-level concern, not a parser-level one.
|
|
34
|
+
|
|
35
|
+
## Token Decomposition
|
|
36
|
+
|
|
37
|
+
Each attribute is decomposed into individual `MLToken` instances:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
·class="container"
|
|
41
|
+
^ ^^ ^
|
|
42
|
+
| || └── endQuote (raw: '"')
|
|
43
|
+
| |└── valueNode (raw: 'container')
|
|
44
|
+
| └── startQuote (raw: '"')
|
|
45
|
+
| equal (raw: '=')
|
|
46
|
+
└── spacesBeforeName (raw: ' ')
|
|
47
|
+
nameNode (raw: 'class')
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
| Token Property | Type | Description |
|
|
51
|
+
| ------------------- | ----------------- | ------------------------------------------------- |
|
|
52
|
+
| `spacesBeforeName` | `MLToken \| null` | Whitespace before the attribute name |
|
|
53
|
+
| `nameNode` | `MLToken \| null` | Attribute name token (null for spread attributes) |
|
|
54
|
+
| `spacesBeforeEqual` | `MLToken \| null` | Whitespace between name and `=` |
|
|
55
|
+
| `equal` | `MLToken \| null` | The `=` sign token |
|
|
56
|
+
| `spacesAfterEqual` | `MLToken \| null` | Whitespace between `=` and value |
|
|
57
|
+
| `startQuote` | `MLToken \| null` | Opening quote token |
|
|
58
|
+
| `valueNode` | `MLToken \| null` | Attribute value token |
|
|
59
|
+
| `endQuote` | `MLToken \| null` | Closing quote token |
|
|
60
|
+
|
|
61
|
+
## Spread Attributes
|
|
62
|
+
|
|
63
|
+
For spread attributes (e.g., `{...props}` in JSX), the `MLAttr` is created with minimal properties:
|
|
64
|
+
|
|
65
|
+
| Property | Value |
|
|
66
|
+
| -------------------- | -------------------------------------------- |
|
|
67
|
+
| `localName` | `'#spread'` |
|
|
68
|
+
| `valueType` | `'code'` |
|
|
69
|
+
| `isDirective` | `true` |
|
|
70
|
+
| `isDynamicValue` | `true` |
|
|
71
|
+
| `isDuplicatable` | `true` |
|
|
72
|
+
| All token properties | `null` |
|
|
73
|
+
| `fix()` | No-op (returns immediately) |
|
|
74
|
+
| `value` | The raw source text of the spread expression |
|
|
75
|
+
|
|
76
|
+
## `tokenList` (MLDomTokenList)
|
|
77
|
+
|
|
78
|
+
Returns a `MLDomTokenList` for space-separated attribute values (e.g., `class` attribute):
|
|
79
|
+
|
|
80
|
+
- If `isDynamicValue` → returns `null` (cannot tokenize a dynamic expression)
|
|
81
|
+
- Otherwise → `new MLDomTokenList(this.value, [this])`
|
|
82
|
+
|
|
83
|
+
## `rule` Getter
|
|
84
|
+
|
|
85
|
+
Delegates to `ownerElement.rule` -- an attribute inherits its rule configuration from the element that owns it.
|
|
86
|
+
|
|
87
|
+
## Metadata Properties
|
|
88
|
+
|
|
89
|
+
| Property | Type | Description |
|
|
90
|
+
| ---------------- | --------------------------------------------- | ------------------------------------------- |
|
|
91
|
+
| `isDynamicValue` | `true \| undefined` | Whether value contains a dynamic expression |
|
|
92
|
+
| `isDirective` | `true \| undefined` | Whether attribute is a framework directive |
|
|
93
|
+
| `isDuplicatable` | `boolean` | Whether attribute may appear multiple times |
|
|
94
|
+
| `valueType` | `'string' \| 'number' \| 'boolean' \| 'code'` | Value type classification |
|
|
95
|
+
| `candidate` | `string \| undefined` | Fix candidate value suggested by the parser |
|
|
96
|
+
|
|
97
|
+
## Methods
|
|
98
|
+
|
|
99
|
+
| Method | Signature | Description |
|
|
100
|
+
| ------------------- | ----------------------------------- | ---------------------------------------------------------------------------------- |
|
|
101
|
+
| `fix` | `fix(raw: string): void` | Updates the `valueNode` fixed content. No-op for spread attributes. |
|
|
102
|
+
| `toNormalizeString` | `toNormalizeString(): string` | Normalized representation stripping extraneous whitespace |
|
|
103
|
+
| `toString` | `toString(fixed?: boolean): string` | Raw or fixed string. For `fixed=true`, reconstructs from each token's fixed value. |
|