@markuplint/ml-core 5.0.0-alpha.1 → 5.0.0-alpha.2
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 +125 -5
- package/ARCHITECTURE.md +125 -5
- package/CHANGELOG.md +14 -0
- package/docs/ml-dom/document.ja.md +7 -14
- package/docs/ml-dom/document.md +7 -14
- package/docs/ml-dom/element.ja.md +9 -24
- package/docs/ml-dom/element.md +9 -24
- package/docs/rule-system.ja.md +1 -1
- package/docs/rule-system.md +1 -1
- package/lib/fix-applier.d.ts +30 -0
- package/lib/fix-applier.js +70 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +1 -0
- package/lib/ml-core.d.ts +14 -2
- package/lib/ml-core.js +25 -4
- package/lib/ml-dom/helper/get-indent.d.ts +0 -1
- package/lib/ml-dom/helper/get-indent.js +5 -18
- package/lib/ml-dom/node/attr.d.ts +2 -13
- package/lib/ml-dom/node/attr.js +3 -35
- package/lib/ml-dom/node/document.d.ts +2 -13
- package/lib/ml-dom/node/document.js +3 -41
- package/lib/ml-dom/node/element-close-tag.d.ts +1 -1
- package/lib/ml-dom/node/element-close-tag.js +2 -16
- package/lib/ml-dom/node/element.d.ts +2 -19
- package/lib/ml-dom/node/element.js +3 -62
- package/lib/ml-dom/token/token.d.ts +3 -18
- package/lib/ml-dom/token/token.js +7 -28
- package/lib/ml-rule/index.d.ts +1 -0
- package/lib/ml-rule/index.js +1 -0
- package/lib/ml-rule/ml-rule-context.d.ts +2 -30
- package/lib/ml-rule/ml-rule-context.js +15 -15
- package/lib/ml-rule/ml-rule.d.ts +6 -10
- package/lib/ml-rule/ml-rule.js +32 -33
- package/lib/ml-rule/rule-fixer.d.ts +25 -0
- package/lib/ml-rule/rule-fixer.js +32 -0
- package/lib/ml-rule/types.d.ts +1 -2
- package/lib/test/index.d.ts +1 -10
- package/lib/test/index.js +0 -11
- package/package.json +12 -12
package/ARCHITECTURE.ja.md
CHANGED
|
@@ -49,9 +49,11 @@ src/
|
|
|
49
49
|
├── ml-rule/
|
|
50
50
|
│ ├── ml-rule.ts — MLRule クラス(ルール実行)
|
|
51
51
|
│ ├── ml-rule-context.ts — MLRuleContext(レポート収集)
|
|
52
|
+
│ ├── rule-fixer.ts — RuleFixer(fix コールバック用の TextEdit ビルダー)
|
|
52
53
|
│ ├── create-rule.ts — createRule ファクトリ
|
|
53
54
|
│ ├── create-test-rule.ts — テスト用ルールファクトリ
|
|
54
55
|
│ └── types.ts — RuleSeed, Checker 型
|
|
56
|
+
├── fix-applier.ts — applyFixes(重複検出付き TextEdit 適用エンジン)
|
|
55
57
|
├── ruleset/
|
|
56
58
|
│ └── index.ts — Ruleset クラス(rules + nodeRules + childNodeRules)
|
|
57
59
|
├── plugin/
|
|
@@ -143,8 +145,8 @@ flowchart LR
|
|
|
143
145
|
|
|
144
146
|
1. **パース**: `MLCore` は設定されたパーサー(`MLParser`)を呼び出し、`MLASTDocument` を生成
|
|
145
147
|
2. **ドキュメント作成**: AST を `MLDocument` でラップし、`createNode()` ファクトリで MLDOM ツリー全体を構築。`RuleMapper` が各ノードのルール設定を解決
|
|
146
|
-
3. **検証**: 各 `MLRule` に対して、`document.setRule(rule)` を呼び出した後 `rule.verify(document)` を実行。ルールは `document.walkOn()` で対象ノードを走査し、`MLRuleContext`
|
|
147
|
-
4. **修正**(オプション): `fix=true`
|
|
148
|
+
3. **検証**: 各 `MLRule` に対して、`document.setRule(rule)` を呼び出した後 `rule.verify(document)` を実行。ルールは `document.walkOn()` で対象ノードを走査し、`MLRuleContext` を通じて違反を報告。ルールは report にインライン `fix` コールバックを付与して `TextEdit` オブジェクトを返す
|
|
149
|
+
4. **修正**(オプション): `fix=true` の場合、report の fix コールバックが `RuleFixer` を使って `TextEdit[]` を生成。`FixApplier.applyFixes(sourceCode, fixes)` が全編集をソーステキストに一括適用(重複検出付き)
|
|
148
150
|
|
|
149
151
|
## MLDOM クラス階層
|
|
150
152
|
|
|
@@ -210,10 +212,9 @@ MLToken<A extends MLASTToken>
|
|
|
210
212
|
| --------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
|
|
211
213
|
| `walkOn(type, walker)` | 指定した型(`'Element'`, `'Text'`, `'Comment'`, `'Attr'`, `'ElementCloseTag'`)のノードを走査 |
|
|
212
214
|
| `setRule(rule)` | 現在のルールを設定(検証時に `MLCore` が使用) |
|
|
213
|
-
| `getTokenList()` | ソース再構築用の全トークンを返す |
|
|
214
215
|
| `searchNodeByLocation(line, col)` | 指定したソース位置のノードを検索 |
|
|
215
216
|
| `getAccessibilityProp(node)` | ARIA アクセシビリティプロパティを計算(`MLElement.getAccessibleName()` のキャッシュ経由でアクセシブルネームを取得) |
|
|
216
|
-
| `toString(
|
|
217
|
+
| `toString()` | ドキュメントの生のソースコードを返す |
|
|
217
218
|
|
|
218
219
|
## MLElement
|
|
219
220
|
|
|
@@ -292,7 +293,7 @@ type RuleSeed<T, O> = {
|
|
|
292
293
|
- `translate` / `t` — ロケール対応のメッセージ翻訳
|
|
293
294
|
- `report(report)` — ノード、メッセージ、オプションの修正とともに違反を報告
|
|
294
295
|
|
|
295
|
-
`provide()` メソッドは `RuleSeed.verify()`
|
|
296
|
+
`provide()` メソッドは `RuleSeed.verify()` に渡されるコンテキストオブジェクトを返します。自動修正ロジックは、個々の `report()` 呼び出しのインライン `fix` コールバックとして提供され、独立したライフサイクルメソッドではありません。
|
|
296
297
|
|
|
297
298
|
### ルール設定の解決
|
|
298
299
|
|
|
@@ -367,6 +368,125 @@ Named nodeRule(設定) 仮想 MLRule(ランタイム)
|
|
|
367
368
|
2. **グループ無効化**: `rules["custom/multi"]: false`(複数エントリの named nodeRule 用)
|
|
368
369
|
3. **名前空間ワイルドカード**: `rules["a11y/*"]: false`(`a11y/` で始まるすべての仮想ルールを無効化)
|
|
369
370
|
|
|
371
|
+
## 自動修正(Autofix)システム
|
|
372
|
+
|
|
373
|
+
自動修正システムは、ルールが違反に対する自動修正を提供する仕組みです。**RuleFixer**(TextEdit ビルダー)、**fix コールバック**(ルール作者が記述するロジック)、**FixApplier**(編集適用エンジン)の 3 つのコンポーネントで構成されます。
|
|
374
|
+
|
|
375
|
+
### 自動修正のデータフロー
|
|
376
|
+
|
|
377
|
+
```mermaid
|
|
378
|
+
flowchart LR
|
|
379
|
+
subgraph RulePhase ["ルールフェーズ"]
|
|
380
|
+
report["context.report({\n message,\n scope,\n fix: コールバック\n})"]
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
subgraph FixPhase ["Fix コールバック実行"]
|
|
384
|
+
callback["fix(fixer) → TextEdit[]"]
|
|
385
|
+
fixer["RuleFixer\n(共有インスタンス)"]
|
|
386
|
+
callback --> fixer
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
subgraph ApplyPhase ["適用フェーズ"]
|
|
390
|
+
fixdata["FixData\n{ edits: TextEdit[] }"]
|
|
391
|
+
applier["applyFixes(\n sourceCode,\n allFixes\n)"]
|
|
392
|
+
output["fixedCode"]
|
|
393
|
+
fixdata --> applier --> output
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
report --> callback
|
|
397
|
+
fixer --> fixdata
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### Fix コールバックの動作
|
|
401
|
+
|
|
402
|
+
ルールは各 `report()` 呼び出しにオプションの `fix` コールバックを付与します。このコールバックはルール検証中には**実行されず**、保存されるだけです。`MLCore.verify()` に `fix=true` が渡された場合にのみ呼び出されます。
|
|
403
|
+
|
|
404
|
+
```mermaid
|
|
405
|
+
sequenceDiagram
|
|
406
|
+
participant Rule as ルール (verify)
|
|
407
|
+
participant Ctx as MLRuleContext
|
|
408
|
+
participant MLR as MLRule.verify()
|
|
409
|
+
participant Fixer as RuleFixer
|
|
410
|
+
participant Core as MLCore.verify()
|
|
411
|
+
participant FA as applyFixes()
|
|
412
|
+
|
|
413
|
+
Rule->>Ctx: report({ scope, message, fix })
|
|
414
|
+
Note over Ctx: fix コールバック付きレポートを格納
|
|
415
|
+
|
|
416
|
+
MLR->>Ctx: context.reports
|
|
417
|
+
loop fix コールバックを持つ各レポート
|
|
418
|
+
MLR->>Fixer: report.fix(sharedFixer)
|
|
419
|
+
Fixer-->>MLR: TextEdit | TextEdit[]
|
|
420
|
+
MLR->>MLR: FixData { edits } としてラップ
|
|
421
|
+
end
|
|
422
|
+
MLR-->>Core: Violation[](FixData 付き)
|
|
423
|
+
|
|
424
|
+
Core->>Core: 全 Violation から FixData を収集
|
|
425
|
+
Core->>FA: applyFixes(sourceCode, allFixes)
|
|
426
|
+
FA-->>Core: FixResult { output, applied, skipped }
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### RuleFixer API
|
|
430
|
+
|
|
431
|
+
`RuleFixer` は `IRuleFixer`(`@markuplint/ml-config` で定義)を実装します。**ステートレス**なヘルパーであり、全ルールで 1 つのインスタンスを共有します。各メソッドはソースコードのレンジ置換を記述する `TextEdit` オブジェクトを生成します。
|
|
432
|
+
|
|
433
|
+
| メソッド | 入力 | 生成される TextEdit |
|
|
434
|
+
| --------------------------- | ------------------------------------ | --------------------------------------- |
|
|
435
|
+
| `replaceText(token, text)` | `startOffset` + `raw` を持つトークン | `range: [start, start+len], text` |
|
|
436
|
+
| `replaceRange(range, text)` | 明示的な `[start, end)` レンジ | `range: [start, end], text` |
|
|
437
|
+
| `insertBefore(token, text)` | `startOffset` を持つトークン | `range: [start, start], text`(ゼロ幅) |
|
|
438
|
+
| `insertAfter(token, text)` | `startOffset` + `raw` を持つトークン | `range: [end, end], text`(ゼロ幅) |
|
|
439
|
+
| `remove(token)` | `startOffset` + `raw` を持つトークン | `range: [start, start+len], text: ""` |
|
|
440
|
+
| `removeRange(range)` | 明示的な `[start, end)` レンジ | `range: [start, end], text: ""` |
|
|
441
|
+
|
|
442
|
+
`token` パラメータは `startOffset` と `raw` プロパティを持つ任意のオブジェクトを受け付けます。MLDOM トークン(`MLToken`, `MLAttr` 等)は自然にこの要件を満たします。
|
|
443
|
+
|
|
444
|
+
### FixApplier アルゴリズム
|
|
445
|
+
|
|
446
|
+
`applyFixes()`(`fix-applier.ts`)は全ルールの `FixData` をマージし、1 パスで適用します:
|
|
447
|
+
|
|
448
|
+
```mermaid
|
|
449
|
+
flowchart TD
|
|
450
|
+
A["展開: FixData[] → タグ付き TextEdit[]"]
|
|
451
|
+
B["ソート: range start 昇順、\nthen range end 降順"]
|
|
452
|
+
C["逐次適用:\n各 edit の重複をチェック"]
|
|
453
|
+
D{{"edit.start < lastAppliedEnd?"}}
|
|
454
|
+
E["スキップ\n(親 FixData をスキップとしてマーク)"]
|
|
455
|
+
F["適用\n(出力にスプライス)"]
|
|
456
|
+
G["分類: 各 FixData を\napplied または skipped に"]
|
|
457
|
+
H["FixResult を返す\n{ output, applied, skipped }"]
|
|
458
|
+
|
|
459
|
+
A --> B --> C --> D
|
|
460
|
+
D -- Yes --> E --> C
|
|
461
|
+
D -- No --> F --> C
|
|
462
|
+
C -. "全 edit 処理完了" .-> G --> H
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
主要な制約:
|
|
466
|
+
|
|
467
|
+
- 1 つの `FixData` 内の edit は互いに重複してはならない
|
|
468
|
+
- `FixData` 間の重複はスキップ機構で処理される
|
|
469
|
+
- `FixData` 内のいずれかの edit がスキップされると、その `FixData` 全体がスキップとして分類される
|
|
470
|
+
|
|
471
|
+
### 実例: ルールの Fix 実装
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// ルールの verify 関数内:
|
|
475
|
+
context.report({
|
|
476
|
+
scope: node,
|
|
477
|
+
message: '属性値にはダブルクォートを使用してください',
|
|
478
|
+
fix: fixer => fixer.replaceText(node.attrValueToken, `"${value}"`),
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
これにより以下の処理が行われます:
|
|
483
|
+
|
|
484
|
+
1. **レポート** → `MLRuleContext` に格納
|
|
485
|
+
2. **Fix コールバック** → `(fixer) => fixer.replaceText(token, text)`(まだ呼び出されない)
|
|
486
|
+
3. **`fix=true` の場合** → 共有 `RuleFixer` でコールバック実行 → `TextEdit` を返す
|
|
487
|
+
4. **TextEdit** → `FixData { edits: [{ range: [12, 17], text: '"hello"' }] }` としてラップ
|
|
488
|
+
5. **applyFixes** → ソースコードに置換を適用
|
|
489
|
+
|
|
370
490
|
## Pretender システム
|
|
371
491
|
|
|
372
492
|
pretender システムにより、コンポーネントをリンティング時にセマンティック HTML 要素として扱うことができます。これにより、ルールがカスタムコンポーネント(例:`<MyButton>`)を標準要素(例:`<button>`)として検証できます。
|
package/ARCHITECTURE.md
CHANGED
|
@@ -49,9 +49,11 @@ src/
|
|
|
49
49
|
├── ml-rule/
|
|
50
50
|
│ ├── ml-rule.ts — MLRule class (rule execution)
|
|
51
51
|
│ ├── ml-rule-context.ts — MLRuleContext (report collection)
|
|
52
|
+
│ ├── rule-fixer.ts — RuleFixer (TextEdit builder for fix callbacks)
|
|
52
53
|
│ ├── create-rule.ts — createRule factory
|
|
53
54
|
│ ├── create-test-rule.ts — Test rule factory
|
|
54
55
|
│ └── types.ts — RuleSeed, Checker types
|
|
56
|
+
├── fix-applier.ts — applyFixes (overlap-aware TextEdit applicator)
|
|
55
57
|
├── ruleset/
|
|
56
58
|
│ └── index.ts — Ruleset class (rules + nodeRules + childNodeRules)
|
|
57
59
|
├── plugin/
|
|
@@ -143,8 +145,8 @@ flowchart LR
|
|
|
143
145
|
|
|
144
146
|
1. **Parse**: `MLCore` invokes the configured parser (`MLParser`) to produce an `MLASTDocument`
|
|
145
147
|
2. **Create Document**: The AST is wrapped in an `MLDocument`, which builds the full MLDOM tree via `createNode()` factory. `RuleMapper` resolves rule configuration for every node
|
|
146
|
-
3. **Verify**: For each `MLRule`, the engine calls `document.setRule(rule)` then `rule.verify(document)`. The rule walks relevant nodes via `document.walkOn()` and reports violations through `MLRuleContext`
|
|
147
|
-
4. **Fix** (optional): When `fix=true`,
|
|
148
|
+
3. **Verify**: For each `MLRule`, the engine calls `document.setRule(rule)` then `rule.verify(document)`. The rule walks relevant nodes via `document.walkOn()` and reports violations through `MLRuleContext`. Rules may attach inline `fix` callbacks to reports that return `TextEdit` objects
|
|
149
|
+
4. **Fix** (optional): When `fix=true`, fix callbacks on reports are executed via `RuleFixer` to produce `TextEdit[]`. `FixApplier.applyFixes(sourceCode, fixes)` applies all edits to the source text with overlap detection
|
|
148
150
|
|
|
149
151
|
## MLDOM Class Hierarchy
|
|
150
152
|
|
|
@@ -210,10 +212,9 @@ The constructor receives an `MLASTDocument`, a `Ruleset`, and an `MLSchema` tupl
|
|
|
210
212
|
| --------------------------------- | ---------------------------------------------------------------------------------------------------------------- |
|
|
211
213
|
| `walkOn(type, walker)` | Walks nodes of a given type (`'Element'`, `'Text'`, `'Comment'`, `'Attr'`, `'ElementCloseTag'`) |
|
|
212
214
|
| `setRule(rule)` | Sets the current rule, used by `MLCore` during verification |
|
|
213
|
-
| `getTokenList()` | Returns all tokens for source reconstruction |
|
|
214
215
|
| `searchNodeByLocation(line, col)` | Finds the node at a given source position |
|
|
215
216
|
| `getAccessibilityProp(node)` | Computes ARIA accessibility properties (delegates to `MLElement.getAccessibleName()` for cached accessible name) |
|
|
216
|
-
| `toString(
|
|
217
|
+
| `toString()` | Returns the raw source code of the document |
|
|
217
218
|
|
|
218
219
|
## MLElement
|
|
219
220
|
|
|
@@ -292,7 +293,7 @@ type RuleSeed<T, O> = {
|
|
|
292
293
|
- `translate` / `t` — Locale-aware message translator
|
|
293
294
|
- `report(report)` — Reports a violation with node, message, and optional fix
|
|
294
295
|
|
|
295
|
-
The `provide()` method returns the context object passed to `RuleSeed.verify()`
|
|
296
|
+
The `provide()` method returns the context object passed to `RuleSeed.verify()`. Auto-fix logic is provided as an inline `fix` callback on individual `report()` calls, not as a separate lifecycle method.
|
|
296
297
|
|
|
297
298
|
### Rule Configuration Resolution
|
|
298
299
|
|
|
@@ -367,6 +368,125 @@ Virtual rules can be disabled at three levels in the `rules` config:
|
|
|
367
368
|
2. **Group disable**: `rules["custom/multi"]: false` (for multi-entry named nodeRules)
|
|
368
369
|
3. **Namespace wildcard**: `rules["a11y/*"]: false` (disables all virtual rules starting with `a11y/`)
|
|
369
370
|
|
|
371
|
+
## Autofix System
|
|
372
|
+
|
|
373
|
+
The autofix system allows rules to provide automatic fixes for violations. It operates through three components: **RuleFixer** (TextEdit builder), **fix callbacks** (rule-authored logic), and **FixApplier** (edit application engine).
|
|
374
|
+
|
|
375
|
+
### Autofix Data Flow
|
|
376
|
+
|
|
377
|
+
```mermaid
|
|
378
|
+
flowchart LR
|
|
379
|
+
subgraph RulePhase ["Rule Phase"]
|
|
380
|
+
report["context.report({\n message,\n scope,\n fix: callback\n})"]
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
subgraph FixPhase ["Fix Callback Execution"]
|
|
384
|
+
callback["fix(fixer) → TextEdit[]"]
|
|
385
|
+
fixer["RuleFixer\n(shared instance)"]
|
|
386
|
+
callback --> fixer
|
|
387
|
+
end
|
|
388
|
+
|
|
389
|
+
subgraph ApplyPhase ["Apply Phase"]
|
|
390
|
+
fixdata["FixData\n{ edits: TextEdit[] }"]
|
|
391
|
+
applier["applyFixes(\n sourceCode,\n allFixes\n)"]
|
|
392
|
+
output["fixedCode"]
|
|
393
|
+
fixdata --> applier --> output
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
report --> callback
|
|
397
|
+
fixer --> fixdata
|
|
398
|
+
```
|
|
399
|
+
|
|
400
|
+
### How Fix Callbacks Work
|
|
401
|
+
|
|
402
|
+
Rules attach an optional `fix` callback to each `report()` call. The callback is **not** executed during rule verification — it is stored and only invoked when `fix=true` is passed to `MLCore.verify()`.
|
|
403
|
+
|
|
404
|
+
```mermaid
|
|
405
|
+
sequenceDiagram
|
|
406
|
+
participant Rule as Rule (verify)
|
|
407
|
+
participant Ctx as MLRuleContext
|
|
408
|
+
participant MLR as MLRule.verify()
|
|
409
|
+
participant Fixer as RuleFixer
|
|
410
|
+
participant Core as MLCore.verify()
|
|
411
|
+
participant FA as applyFixes()
|
|
412
|
+
|
|
413
|
+
Rule->>Ctx: report({ scope, message, fix })
|
|
414
|
+
Note over Ctx: Stores report with fix callback
|
|
415
|
+
|
|
416
|
+
MLR->>Ctx: context.reports
|
|
417
|
+
loop Each report with fix callback
|
|
418
|
+
MLR->>Fixer: report.fix(sharedFixer)
|
|
419
|
+
Fixer-->>MLR: TextEdit | TextEdit[]
|
|
420
|
+
MLR->>MLR: Wrap as FixData { edits }
|
|
421
|
+
end
|
|
422
|
+
MLR-->>Core: Violation[] (with FixData)
|
|
423
|
+
|
|
424
|
+
Core->>Core: Collect all FixData from violations
|
|
425
|
+
Core->>FA: applyFixes(sourceCode, allFixes)
|
|
426
|
+
FA-->>Core: FixResult { output, applied, skipped }
|
|
427
|
+
```
|
|
428
|
+
|
|
429
|
+
### RuleFixer API
|
|
430
|
+
|
|
431
|
+
`RuleFixer` implements `IRuleFixer` (defined in `@markuplint/ml-config`). It is a **stateless** helper — a single instance is shared across all rules. Each method builds a `TextEdit` object describing a range replacement on the source code.
|
|
432
|
+
|
|
433
|
+
| Method | Input | TextEdit Produced |
|
|
434
|
+
| --------------------------- | -------------------------------- | ------------------------------------------ |
|
|
435
|
+
| `replaceText(token, text)` | Token with `startOffset` + `raw` | `range: [start, start+len], text` |
|
|
436
|
+
| `replaceRange(range, text)` | Explicit `[start, end)` range | `range: [start, end], text` |
|
|
437
|
+
| `insertBefore(token, text)` | Token with `startOffset` | `range: [start, start], text` (zero-width) |
|
|
438
|
+
| `insertAfter(token, text)` | Token with `startOffset` + `raw` | `range: [end, end], text` (zero-width) |
|
|
439
|
+
| `remove(token)` | Token with `startOffset` + `raw` | `range: [start, start+len], text: ""` |
|
|
440
|
+
| `removeRange(range)` | Explicit `[start, end)` range | `range: [start, end], text: ""` |
|
|
441
|
+
|
|
442
|
+
The `token` parameter accepts any object with `startOffset` and `raw` properties. MLDOM tokens (`MLToken`, `MLAttr`, etc.) satisfy this naturally.
|
|
443
|
+
|
|
444
|
+
### FixApplier Algorithm
|
|
445
|
+
|
|
446
|
+
`applyFixes()` (in `fix-applier.ts`) merges all `FixData` from all rules and applies them in a single pass:
|
|
447
|
+
|
|
448
|
+
```mermaid
|
|
449
|
+
flowchart TD
|
|
450
|
+
A["Flatten: FixData[] → tagged TextEdit[]"]
|
|
451
|
+
B["Sort: by range start ascending,\nthen range end descending"]
|
|
452
|
+
C["Apply sequentially:\nfor each edit, check overlap"]
|
|
453
|
+
D{{"edit.start < lastAppliedEnd?"}}
|
|
454
|
+
E["Skip edit\n(mark parent FixData as skipped)"]
|
|
455
|
+
F["Apply edit\n(splice into output)"]
|
|
456
|
+
G["Classify: each FixData as\napplied or skipped"]
|
|
457
|
+
H["Return FixResult\n{ output, applied, skipped }"]
|
|
458
|
+
|
|
459
|
+
A --> B --> C --> D
|
|
460
|
+
D -- Yes --> E --> C
|
|
461
|
+
D -- No --> F --> C
|
|
462
|
+
C -. "all edits processed" .-> G --> H
|
|
463
|
+
```
|
|
464
|
+
|
|
465
|
+
Key constraints:
|
|
466
|
+
|
|
467
|
+
- Edits within a single `FixData` must not overlap each other
|
|
468
|
+
- Inter-`FixData` overlap is handled by the skip mechanism
|
|
469
|
+
- If any edit in a `FixData` is skipped, the entire `FixData` is classified as skipped
|
|
470
|
+
|
|
471
|
+
### Example: Rule Fix in Practice
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
// In a rule's verify function:
|
|
475
|
+
context.report({
|
|
476
|
+
scope: node,
|
|
477
|
+
message: 'Attribute value must use double quotes',
|
|
478
|
+
fix: fixer => fixer.replaceText(node.attrValueToken, `"${value}"`),
|
|
479
|
+
});
|
|
480
|
+
```
|
|
481
|
+
|
|
482
|
+
This produces:
|
|
483
|
+
|
|
484
|
+
1. **Report** → stored in `MLRuleContext`
|
|
485
|
+
2. **Fix callback** → `(fixer) => fixer.replaceText(token, text)` (not yet called)
|
|
486
|
+
3. **When `fix=true`** → callback invoked with shared `RuleFixer` → returns `TextEdit`
|
|
487
|
+
4. **TextEdit** → wrapped as `FixData { edits: [{ range: [12, 17], text: '"hello"' }] }`
|
|
488
|
+
5. **applyFixes** → splices the replacement into source code
|
|
489
|
+
|
|
370
490
|
## Pretender System
|
|
371
491
|
|
|
372
492
|
The pretender system allows components to be treated as semantic HTML elements during linting. This enables rules to validate custom components (e.g., `<MyButton>`) as if they were standard elements (e.g., `<button>`).
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,20 @@
|
|
|
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
|
+
# [5.0.0-alpha.2](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.1...v5.0.0-alpha.2) (2026-02-23)
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
- **ml-core:** implement autofix engine with fix-applier and rule-fixer ([36efcec](https://github.com/markuplint/markuplint/commit/36efcecb17e2f4e0729390b1684e571c13c38a38))
|
|
11
|
+
|
|
12
|
+
### BREAKING CHANGES
|
|
13
|
+
|
|
14
|
+
- **ml-core:** verify() now returns VerifyResult instead of
|
|
15
|
+
Violation[]. RuleSeed.fix() is removed in favor of inline fix
|
|
16
|
+
callbacks on report().
|
|
17
|
+
|
|
18
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
19
|
+
|
|
6
20
|
# [5.0.0-alpha.1](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.0...v5.0.0-alpha.1) (2026-02-22)
|
|
7
21
|
|
|
8
22
|
**Note:** Version bump only for package @markuplint/ml-core
|
|
@@ -109,15 +109,9 @@ await document.walkOn(
|
|
|
109
109
|
|
|
110
110
|
`AccessibilityProperties` を返します([型ユーティリティ](./helpers.ja.md#型ユーティリティ)を参照)。
|
|
111
111
|
|
|
112
|
-
## `toString(
|
|
112
|
+
## `toString()`
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- `fixed=false`(デフォルト):元の `raw` 文字列を返す
|
|
117
|
-
- `fixed=true`:オフセット追跡による置換を適用する:
|
|
118
|
-
1. `getTokenList()` を取得する(`startOffset` でソート済み)
|
|
119
|
-
2. 各トークンに対して:`toString(true) !== raw` の場合、修正済みコンテンツを文字列にスプライスする
|
|
120
|
-
3. 正確な位置を維持するために累積オフセット差を追跡する
|
|
114
|
+
ドキュメントの元の `raw` ソース文字列を返します。修正は DOM ノードの変更とトークンリストの再構築を通じて適用されなくなりました。代わりに、レポートの修正コールバックが `RuleFixer` を通じて実行され、`TextEdit[]` を生成します。`FixApplier.applyFixes()` がすべての編集をソーステキストに直接適用します。
|
|
121
115
|
|
|
122
116
|
## `defaultView`
|
|
123
117
|
|
|
@@ -125,12 +119,11 @@ await document.walkOn(
|
|
|
125
119
|
|
|
126
120
|
## その他のメソッド
|
|
127
121
|
|
|
128
|
-
| メソッド | シグネチャ | 説明
|
|
129
|
-
| ---------------------- | ------------------------------------------------- |
|
|
130
|
-
| `setRule` | `setRule(rule: MLRule \| null): void` | 現在評価中のルールを設定/クリアする
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `debugMap` | `debugMap(): string[]` | ドキュメントツリー構造のデバッグ出力 |
|
|
122
|
+
| メソッド | シグネチャ | 説明 |
|
|
123
|
+
| ---------------------- | ------------------------------------------------- | ------------------------------------- |
|
|
124
|
+
| `setRule` | `setRule(rule: MLRule \| null): void` | 現在評価中のルールを設定/クリアする |
|
|
125
|
+
| `searchNodeByLocation` | `searchNodeByLocation(line, col): MLNode \| null` | 1始まりのソース位置でノードを検索する |
|
|
126
|
+
| `debugMap` | `debugMap(): string[]` | ドキュメントツリー構造のデバッグ出力 |
|
|
134
127
|
|
|
135
128
|
## endTag モード
|
|
136
129
|
|
package/docs/ml-dom/document.md
CHANGED
|
@@ -109,15 +109,9 @@ Computes ARIA accessibility properties for a node.
|
|
|
109
109
|
|
|
110
110
|
Returns `AccessibilityProperties` (see [Type Utilities](./helpers.md#type-utilities)).
|
|
111
111
|
|
|
112
|
-
## `toString(
|
|
112
|
+
## `toString()`
|
|
113
113
|
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
- `fixed=false` (default): Returns the original `raw` string
|
|
117
|
-
- `fixed=true`: Applies offset-tracked replacement:
|
|
118
|
-
1. Get `getTokenList()` (sorted by `startOffset`)
|
|
119
|
-
2. For each token: if `toString(true) !== raw`, splice the fixed content into the string
|
|
120
|
-
3. Track cumulative offset differences to maintain correct positions
|
|
114
|
+
Returns the original `raw` source string of the document. Fixes are no longer applied through DOM node mutation and token-list reconstruction. Instead, fix callbacks on reports are executed via `RuleFixer` to produce `TextEdit[]`. `FixApplier.applyFixes()` applies all edits to the source text.
|
|
121
115
|
|
|
122
116
|
## `defaultView`
|
|
123
117
|
|
|
@@ -125,12 +119,11 @@ Returns a mock window object providing `getComputedStyle()` that returns an obje
|
|
|
125
119
|
|
|
126
120
|
## Other Methods
|
|
127
121
|
|
|
128
|
-
| Method | Signature | Description
|
|
129
|
-
| ---------------------- | ------------------------------------------------- |
|
|
130
|
-
| `setRule` | `setRule(rule: MLRule \| null): void` | Set/clear the current rule being evaluated
|
|
131
|
-
| `
|
|
132
|
-
| `
|
|
133
|
-
| `debugMap` | `debugMap(): string[]` | Debug output of document tree structure |
|
|
122
|
+
| Method | Signature | Description |
|
|
123
|
+
| ---------------------- | ------------------------------------------------- | ------------------------------------------ |
|
|
124
|
+
| `setRule` | `setRule(rule: MLRule \| null): void` | Set/clear the current rule being evaluated |
|
|
125
|
+
| `searchNodeByLocation` | `searchNodeByLocation(line, col): MLNode \| null` | Find node at a 1-based source position |
|
|
126
|
+
| `debugMap` | `debugMap(): string[]` | Debug output of document tree structure |
|
|
134
127
|
|
|
135
128
|
## endTag Modes
|
|
136
129
|
|
|
@@ -8,20 +8,18 @@ HTML/SVG/MathML の要素ノードです。`MLParentNode` を継承し、`Elemen
|
|
|
8
8
|
|
|
9
9
|
要素には異なる目的に使用される複数の名前関連プロパティがあります:
|
|
10
10
|
|
|
11
|
-
| プロパティ
|
|
12
|
-
|
|
|
13
|
-
| `localName`
|
|
14
|
-
| `nodeName`
|
|
15
|
-
| `rawName`
|
|
16
|
-
| `
|
|
17
|
-
| `tagName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
|
|
11
|
+
| プロパティ | HTML `<DIV>` | SVG `<foreignObject>` | Pretender(`MyButton` → `button`) |
|
|
12
|
+
| ----------- | ------------ | --------------------- | ---------------------------------- |
|
|
13
|
+
| `localName` | `"div"` | `"foreignObject"` | `"button"` |
|
|
14
|
+
| `nodeName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
|
|
15
|
+
| `rawName` | `"DIV"` | `"foreignObject"` | `"MyButton"` |
|
|
16
|
+
| `tagName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
|
|
18
17
|
|
|
19
18
|
**ルール:**
|
|
20
19
|
|
|
21
20
|
- **`localName`**: HTML 要素 → 小文字化。外部要素または非 `'html'` の elementType → そのまま。pretender コンテキスト → pretender の `localName`。`tagNameCaseSensitive` が `true` の場合 → 小文字化しない。
|
|
22
21
|
- **`nodeName`**: HTML 要素 → 大文字化(DOM 慣例)。外部要素または非 `'html'` の elementType → AST からそのまま。pretender コンテキスト → pretender の `nodeName`。
|
|
23
22
|
- **`rawName`**: 常に元の AST `nodeName`。正規化なし、pretender の影響なし。
|
|
24
|
-
- **`fixedNodeName`**: 初期値は `rawName`。リント修正がタグ名を変更した場合に `fixNodeName(name)` で更新される。
|
|
25
23
|
- **`tagName`**: `nodeName` と同じ(pretender コンテキストに従う)。
|
|
26
24
|
|
|
27
25
|
## 要素型の判定
|
|
@@ -98,7 +96,6 @@ Pretender システムのアーキテクチャ、初期化フロー、プロパ
|
|
|
98
96
|
|
|
99
97
|
| メソッド | シグネチャ | 説明 |
|
|
100
98
|
| ------------------------ | ------------------------------------------------- | ---------------------------------------------------------------------------------- |
|
|
101
|
-
| `fixNodeName` | `fixNodeName(name: string): void` | リント自動修正用に `fixedNodeName` を更新する |
|
|
102
99
|
| `getAccessibleName` | `getAccessibleName(version: ARIAVersion): string` | `getAccname()` を通じてアクセシブル名を計算する |
|
|
103
100
|
| `toNormalizeString` | `toNormalizeString(): string` | 比較用の正規化された表現を返す(キャッシュ済み)。子要素と属性を再帰的に正規化する |
|
|
104
101
|
| `nextElementSibling` | `get nextElementSibling: MLElement \| null` | 次の兄弟要素 |
|
|
@@ -134,7 +131,7 @@ Pretender システムのアーキテクチャ、初期化フロー、プロパ
|
|
|
134
131
|
|
|
135
132
|
- 対応するソーストークンを持たない
|
|
136
133
|
- `prevToken` によってスキップされる(有効なオフセットチェーンを維持するため)
|
|
137
|
-
- `toString(
|
|
134
|
+
- `toString()` から `raw` を返す(ソースに対応するものがないため)
|
|
138
135
|
- `getChildElementsAndTextNodeWithoutWhitespaces()` によってフラット化される
|
|
139
136
|
|
|
140
137
|
## 閉じタグ
|
|
@@ -143,21 +140,9 @@ Pretender システムのアーキテクチャ、初期化フロー、プロパ
|
|
|
143
140
|
| ---------- | --------------------------- | ------------------------------------------------------------------------------------ |
|
|
144
141
|
| `closeTag` | `MLElementCloseTag \| null` | ペアの閉じタグ。void 要素、自己閉じ要素、または `endTag === 'never'` の場合は `null` |
|
|
145
142
|
|
|
146
|
-
## `toString(
|
|
143
|
+
## `toString()`
|
|
147
144
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
- `fixed=false` または pretender/省略要素/`#` プレフィックスの nodeName → `raw` を返す
|
|
151
|
-
- `fixed=true`:
|
|
152
|
-
1. 置換可能なノードのリストを構築する:`[tagOpenChar + fixedNodeName, ...overriddenCommentNodes, ...attributes]`
|
|
153
|
-
2. 各ノードに対して、正しいオフセットで `node.toString(true)` をスプライスする
|
|
154
|
-
3. 正確な位置決めのために累積オフセット差を追跡する
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
Original: <DIV class="foo" >
|
|
158
|
-
Fixed: <div class="foo" >
|
|
159
|
-
^^^^ (fixedNodeName が "DIV" から "div" に変更)
|
|
160
|
-
```
|
|
145
|
+
要素の生のソース文字列を返します。このメソッドはパースされたドキュメントに記述されたままの元の `raw` ソーステキストを単純に返します。修正は DOM ノードの変更を通じて適用されなくなりました。代わりに、修正操作は `RuleFixer` を通じて `TextEdit[]` を生成し、`FixApplier.applyFixes()` がすべての編集をソーステキストに直接適用します。
|
|
161
146
|
|
|
162
147
|
## その他のプロパティ
|
|
163
148
|
|
package/docs/ml-dom/element.md
CHANGED
|
@@ -8,20 +8,18 @@ HTML/SVG/MathML element node. Extends `MLParentNode` and implements `Element`, `
|
|
|
8
8
|
|
|
9
9
|
The element has multiple name-related properties that serve different purposes:
|
|
10
10
|
|
|
11
|
-
| Property
|
|
12
|
-
|
|
|
13
|
-
| `localName`
|
|
14
|
-
| `nodeName`
|
|
15
|
-
| `rawName`
|
|
16
|
-
| `
|
|
17
|
-
| `tagName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
|
|
11
|
+
| Property | HTML `<DIV>` | SVG `<foreignObject>` | Pretender (`MyButton` → `button`) |
|
|
12
|
+
| ----------- | ------------ | --------------------- | --------------------------------- |
|
|
13
|
+
| `localName` | `"div"` | `"foreignObject"` | `"button"` |
|
|
14
|
+
| `nodeName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
|
|
15
|
+
| `rawName` | `"DIV"` | `"foreignObject"` | `"MyButton"` |
|
|
16
|
+
| `tagName` | `"DIV"` | `"foreignObject"` | `"BUTTON"` |
|
|
18
17
|
|
|
19
18
|
**Rules:**
|
|
20
19
|
|
|
21
20
|
- **`localName`**: HTML elements → lowercased. Foreign elements or non-`'html'` elementType → as-is. Pretender context → pretender's `localName`. If `tagNameCaseSensitive` is `true` → no lowercasing.
|
|
22
21
|
- **`nodeName`**: HTML elements → uppercased (DOM convention). Foreign elements or non-`'html'` elementType → as-is from AST. Pretender context → pretender's `nodeName`.
|
|
23
22
|
- **`rawName`**: Always the original AST `nodeName`, with no normalization and no pretender influence.
|
|
24
|
-
- **`fixedNodeName`**: Starts as `rawName`. Updated by `fixNodeName(name)` when a lint fix modifies the tag name.
|
|
25
23
|
- **`tagName`**: Same as `nodeName` (follows pretender context).
|
|
26
24
|
|
|
27
25
|
## Element Type Resolution
|
|
@@ -98,7 +96,6 @@ For comprehensive documentation on the pretender system's architecture, initiali
|
|
|
98
96
|
|
|
99
97
|
| Method | Signature | Description |
|
|
100
98
|
| ------------------------ | ------------------------------------------------- | ------------------------------------------------------------------------------------------------------------ |
|
|
101
|
-
| `fixNodeName` | `fixNodeName(name: string): void` | Updates `fixedNodeName` for lint auto-fix of the tag name |
|
|
102
99
|
| `getAccessibleName` | `getAccessibleName(version: ARIAVersion): string` | Computes the accessible name via `getAccname()` |
|
|
103
100
|
| `toNormalizeString` | `toNormalizeString(): string` | Returns a normalized representation for comparison (cached). Recursively normalizes children and attributes. |
|
|
104
101
|
| `nextElementSibling` | `get nextElementSibling: MLElement \| null` | Next sibling element |
|
|
@@ -134,7 +131,7 @@ Elements with `isOmitted === true` were implicitly inserted by the parser (e.g.,
|
|
|
134
131
|
|
|
135
132
|
- Have no corresponding source tokens
|
|
136
133
|
- Are skipped by `prevToken` (to maintain valid offset chains)
|
|
137
|
-
- Return `raw` from `toString(
|
|
134
|
+
- Return `raw` from `toString()` (there's nothing in the source to represent)
|
|
138
135
|
- Are flattened by `getChildElementsAndTextNodeWithoutWhitespaces()`
|
|
139
136
|
|
|
140
137
|
## Close Tag
|
|
@@ -143,21 +140,9 @@ Elements with `isOmitted === true` were implicitly inserted by the parser (e.g.,
|
|
|
143
140
|
| ---------- | --------------------------- | ------------------------------------------------------------------------------------------------ |
|
|
144
141
|
| `closeTag` | `MLElementCloseTag \| null` | Paired close tag. `null` for void elements, self-closing elements, or when `endTag === 'never'`. |
|
|
145
142
|
|
|
146
|
-
## `toString(
|
|
143
|
+
## `toString()`
|
|
147
144
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
- `fixed=false` or pretender/omitted/`#`-prefixed nodeName → return `raw`
|
|
151
|
-
- `fixed=true`:
|
|
152
|
-
1. Build a list of replaceable nodes: `[tagOpenChar + fixedNodeName, ...overriddenCommentNodes, ...attributes]`
|
|
153
|
-
2. For each node, splice `node.toString(true)` at the correct offset
|
|
154
|
-
3. Track cumulative offset differences for accurate positioning
|
|
155
|
-
|
|
156
|
-
```
|
|
157
|
-
Original: <DIV class="foo" >
|
|
158
|
-
Fixed: <div class="foo" >
|
|
159
|
-
^^^^ (fixedNodeName changed from "DIV" to "div")
|
|
160
|
-
```
|
|
145
|
+
Returns the element's raw source string. This method simply returns the original `raw` source text as it appeared in the parsed document. Fixes are no longer applied through DOM node mutation; instead, fix operations produce `TextEdit[]` via `RuleFixer`, and `FixApplier.applyFixes()` applies all edits directly to the source text.
|
|
161
146
|
|
|
162
147
|
## Other Properties
|
|
163
148
|
|
package/docs/rule-system.ja.md
CHANGED
|
@@ -189,7 +189,7 @@ constructor(document: MLDocument<T, O>, locale: LocaleSet)
|
|
|
189
189
|
|
|
190
190
|
### `provide(): ProvidedContext`
|
|
191
191
|
|
|
192
|
-
`RuleSeed.verify()`
|
|
192
|
+
`RuleSeed.verify()` に渡されるコンテキストオブジェクトを返します。自動修正ロジックは個々の `report()` 呼び出しのインライン `fix` コールバックとして提供されます:
|
|
193
193
|
|
|
194
194
|
```typescript
|
|
195
195
|
{
|
package/docs/rule-system.md
CHANGED
|
@@ -189,7 +189,7 @@ Creates translator from locale, stores document reference.
|
|
|
189
189
|
|
|
190
190
|
### `provide(): ProvidedContext`
|
|
191
191
|
|
|
192
|
-
Returns the context object passed to `RuleSeed.verify()`
|
|
192
|
+
Returns the context object passed to `RuleSeed.verify()`. Auto-fix logic is provided as an inline `fix` callback on individual `report()` calls:
|
|
193
193
|
|
|
194
194
|
```typescript
|
|
195
195
|
{
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { FixData } from '@markuplint/ml-config';
|
|
2
|
+
/**
|
|
3
|
+
* The result of applying fixes to source code.
|
|
4
|
+
*/
|
|
5
|
+
export type FixResult = {
|
|
6
|
+
/** The resulting source code after applying fixes */
|
|
7
|
+
readonly output: string;
|
|
8
|
+
/** Fixes that were successfully applied */
|
|
9
|
+
readonly applied: readonly FixData[];
|
|
10
|
+
/** Fixes that were skipped due to overlapping ranges */
|
|
11
|
+
readonly skipped: readonly FixData[];
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Applies a set of text edits to the source code.
|
|
15
|
+
*
|
|
16
|
+
* **Constraint**: Edits within a single FixData must not overlap each other,
|
|
17
|
+
* and should ideally be ordered by range[0]. Inter-FixData overlap is handled
|
|
18
|
+
* by the skip mechanism, but intra-FixData overlap leads to undefined behavior.
|
|
19
|
+
*
|
|
20
|
+
* Algorithm (modeled after ESLint's SourceCodeFixer):
|
|
21
|
+
* 1. Flatten all FixData.edits into individual edits, each tagged with its parent FixData
|
|
22
|
+
* 2. Sort by range[0] ascending (ties broken by range[1] descending)
|
|
23
|
+
* 3. Apply edits sequentially; skip any edit whose range overlaps a previously applied edit
|
|
24
|
+
* 4. Classify each FixData as applied (all edits applied) or skipped (any edit skipped)
|
|
25
|
+
*
|
|
26
|
+
* @param sourceCode - The original source code
|
|
27
|
+
* @param fixes - The fix data to apply
|
|
28
|
+
* @returns The result containing the fixed code and applied/skipped classification
|
|
29
|
+
*/
|
|
30
|
+
export declare function applyFixes(sourceCode: string, fixes: readonly FixData[]): FixResult;
|