@markuplint/ml-core 4.13.2 → 4.13.3
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 +467 -0
- package/ARCHITECTURE.md +467 -0
- package/CHANGELOG.md +3 -3
- package/README.md +5 -0
- package/SKILL.md +61 -0
- package/docs/linting-pipeline.ja.md +303 -0
- package/docs/linting-pipeline.md +303 -0
- package/docs/maintenance.ja.md +210 -0
- package/docs/maintenance.md +210 -0
- package/docs/ml-dom/attr.ja.md +95 -0
- package/docs/ml-dom/attr.md +95 -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 +200 -0
- package/docs/ml-dom/node.md +200 -0
- package/docs/ml-dom/others.ja.md +119 -0
- package/docs/ml-dom/others.md +119 -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 +270 -0
- package/docs/rule-system.md +270 -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/ml-core.d.ts +36 -0
- package/lib/ml-core.js +29 -0
- package/lib/ml-dom/helper/get-indent.d.ts +4 -1
- package/lib/ml-dom/helper/get-indent.js +4 -1
- package/lib/ml-dom/node/attr.d.ts +65 -4
- package/lib/ml-dom/node/attr.js +53 -4
- package/lib/ml-dom/node/block.d.ts +21 -0
- package/lib/ml-dom/node/block.js +14 -0
- 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 +13 -0
- package/lib/ml-dom/node/document.d.ts +74 -4
- package/lib/ml-dom/node/document.js +57 -2
- package/lib/ml-dom/node/element.d.ts +136 -2
- package/lib/ml-dom/node/element.js +115 -2
- package/lib/ml-dom/node/node.d.ts +16 -0
- package/lib/ml-dom/node/node.js +16 -0
- package/lib/ml-dom/node/text.d.ts +12 -0
- package/lib/ml-dom/node/text.js +12 -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 +36 -0
- package/lib/ml-rule/create-rule.d.ts +9 -0
- package/lib/ml-rule/create-rule.js +9 -0
- package/lib/ml-rule/ml-rule.d.ts +33 -0
- package/lib/ml-rule/ml-rule.js +30 -0
- 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 +7 -0
- package/lib/test/index.d.ts +42 -1
- package/lib/test/index.js +35 -1
- package/lib/types.d.ts +8 -0
- package/lib/violation-collector.d.ts +33 -0
- package/lib/violation-collector.js +33 -0
- package/package.json +12 -12
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
# Rule Mapping — How Rules Are Applied to Nodes
|
|
2
|
+
|
|
3
|
+
**Source:** `src/ml-dom/node/document.ts` (`_ruleMapping()`), `src/ml-dom/node/rule-mapper.ts` (`RuleMapper`)
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
Rule mapping is the process of distributing rule configurations from the user's config to individual MLDOM nodes. Each node has a `rules` property (`Record<string, AnyRule>`) that stores the resolved rule configuration for every rule that applies to it.
|
|
8
|
+
|
|
9
|
+
The mapping uses three configuration layers — `rules`, `nodeRules`, and `childNodeRules` — processed in a defined order, with CSS selector specificity used to resolve conflicts.
|
|
10
|
+
|
|
11
|
+
For configuration syntax, see the [markuplint configuration documentation](https://markuplint.dev/docs/configuration).
|
|
12
|
+
|
|
13
|
+
## Architecture
|
|
14
|
+
|
|
15
|
+
### When Rule Mapping Happens
|
|
16
|
+
|
|
17
|
+
Rule mapping happens **once** during `MLDocument` construction, after pretender initialization and before any rule execution:
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
MLDocument constructor
|
|
21
|
+
├── 1. Parse AST → create MLDOM nodes (nodeList)
|
|
22
|
+
├── 2. _pretending(pretenders)
|
|
23
|
+
├── 3. _ruleMapping(ruleset) ← Rule mapping
|
|
24
|
+
└── 4. (ready for rule verification)
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
This ordering matters:
|
|
28
|
+
|
|
29
|
+
- Pretenders must be established first, so that selector matching in rule mapping can match against pretender identities (e.g., a `nodeRules` entry targeting `button` should match `<MyButton>` pretending to be `<button>`)
|
|
30
|
+
- Rule mapping must complete before verification, so that rules can read `node.rules` during `walkOn()`
|
|
31
|
+
|
|
32
|
+
### Components
|
|
33
|
+
|
|
34
|
+
| Component | Source | Role |
|
|
35
|
+
| ---------------- | -------------------------------- | ------------------------------------------------------------------------- |
|
|
36
|
+
| `Ruleset` | `src/ruleset/index.ts` | Extracts `rules`, `nodeRules`, `childNodeRules` from user config |
|
|
37
|
+
| `RuleMapper` | `src/ml-dom/node/rule-mapper.ts` | Accumulates rule-to-node mappings with specificity, applies them to nodes |
|
|
38
|
+
| `_ruleMapping()` | `src/ml-dom/node/document.ts` | Orchestrates the three-layer processing |
|
|
39
|
+
|
|
40
|
+
## The Three Layers
|
|
41
|
+
|
|
42
|
+
### Layer 1: Global Rules (`rules`)
|
|
43
|
+
|
|
44
|
+
```json
|
|
45
|
+
{
|
|
46
|
+
"rules": {
|
|
47
|
+
"attr-duplication": true,
|
|
48
|
+
"case-sensitive-tag-name": "warning"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Global rules are applied to **every node** in the document, including the `#document` node itself. They have a fixed specificity of `[0, 0, 0]`.
|
|
54
|
+
|
|
55
|
+
**Processing:**
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// Apply to #document
|
|
59
|
+
for (const ruleName of Object.keys(ruleset.rules)) {
|
|
60
|
+
ruleMapper.set(document, ruleName, {
|
|
61
|
+
from: 'rules',
|
|
62
|
+
specificity: [0, 0, 0],
|
|
63
|
+
rule,
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Apply to every node in nodeList
|
|
68
|
+
for (const node of document.nodeList) {
|
|
69
|
+
for (const ruleName of Object.keys(ruleset.rules)) {
|
|
70
|
+
ruleMapper.set(node, ruleName, {
|
|
71
|
+
from: 'rules',
|
|
72
|
+
specificity: [0, 0, 0],
|
|
73
|
+
rule,
|
|
74
|
+
});
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Layer 2: Node Rules (`nodeRules`)
|
|
80
|
+
|
|
81
|
+
```json
|
|
82
|
+
{
|
|
83
|
+
"nodeRules": [
|
|
84
|
+
{
|
|
85
|
+
"selector": "img",
|
|
86
|
+
"rules": {
|
|
87
|
+
"required-attr": { "value": "alt" }
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
]
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Node rules override global rules for **elements that match the selector**. Only `ELEMENT_NODE` and `TEXT_NODE` are considered; text nodes cannot be selector targets, so only elements are actually matched.
|
|
95
|
+
|
|
96
|
+
**Processing:**
|
|
97
|
+
|
|
98
|
+
1. For each `nodeRule` entry, check if the current element matches via `matchMLSelector(selector)`
|
|
99
|
+
2. If matched, for each rule in the entry:
|
|
100
|
+
- `exchangeValueOnRule(rule, matches.data)` — render Mustache template variables from regex selector captures
|
|
101
|
+
- `mergeRule(globalRule, convertedRule)` — merge with the global rule config (see [Merging](#merging-with-global-rules))
|
|
102
|
+
- `ruleMapper.set(node, ruleName, { from: 'nodeRules', specificity: matches.specificity, rule: mergedRule })`
|
|
103
|
+
|
|
104
|
+
The specificity comes from the CSS selector used to match the element.
|
|
105
|
+
|
|
106
|
+
### Layer 3: Child Node Rules (`childNodeRules`)
|
|
107
|
+
|
|
108
|
+
```json
|
|
109
|
+
{
|
|
110
|
+
"childNodeRules": [
|
|
111
|
+
{
|
|
112
|
+
"selector": "table",
|
|
113
|
+
"inheritance": true,
|
|
114
|
+
"rules": {
|
|
115
|
+
"class-naming": "/^table-/"
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
]
|
|
119
|
+
}
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Child node rules apply rules to **children (or descendants)** of matched elements. The selector matches the **parent**, and the rules are distributed to its children.
|
|
123
|
+
|
|
124
|
+
**Processing:**
|
|
125
|
+
|
|
126
|
+
1. For each `childNodeRule` entry, check if the current element matches via `matchMLSelector(selector)`
|
|
127
|
+
2. If matched, determine the target nodes:
|
|
128
|
+
- `inheritance: true` → all **descendants** (collected via `syncWalk`)
|
|
129
|
+
- `inheritance: false` (default) → only **direct children** (`childNodes`)
|
|
130
|
+
3. For each rule in the entry, apply the merged rule to every target node
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
const targetDescendants = nodeRule.inheritance ? descendants : children;
|
|
134
|
+
|
|
135
|
+
for (const descendant of targetDescendants) {
|
|
136
|
+
ruleMapper.set(descendant, ruleName, {
|
|
137
|
+
from: 'childNodeRules',
|
|
138
|
+
specificity: matches.specificity,
|
|
139
|
+
rule: mergedRule,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Note: the specificity is that of the **parent's** selector match, not the child's.
|
|
145
|
+
|
|
146
|
+
## Processing Order
|
|
147
|
+
|
|
148
|
+
The three layers are processed in a specific order within `_ruleMapping()`:
|
|
149
|
+
|
|
150
|
+
```
|
|
151
|
+
For the #document node:
|
|
152
|
+
1. Apply all global rules (specificity [0,0,0])
|
|
153
|
+
|
|
154
|
+
For each node in nodeList:
|
|
155
|
+
2. Apply all global rules (specificity [0,0,0])
|
|
156
|
+
3. Apply matching nodeRules (selector specificity)
|
|
157
|
+
4. Apply matching childNodeRules (selector specificity of parent)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
Because `RuleMapper.set()` resolves conflicts by specificity, the processing order within the same specificity level matters:
|
|
161
|
+
|
|
162
|
+
- **Same specificity**: later `set()` calls overwrite earlier ones (last-write-wins)
|
|
163
|
+
- **Higher specificity**: always wins regardless of order
|
|
164
|
+
- **Lower specificity**: silently skipped
|
|
165
|
+
|
|
166
|
+
This means:
|
|
167
|
+
|
|
168
|
+
| Scenario | Winner |
|
|
169
|
+
| ------------------------------------------------------------------------ | ---------------------------------------------------------- |
|
|
170
|
+
| `rules` vs `nodeRules` (any selector) | `nodeRules` (selector specificity `≥ [0,0,1]` > `[0,0,0]`) |
|
|
171
|
+
| `rules` vs `childNodeRules` (any selector) | `childNodeRules` (same reasoning) |
|
|
172
|
+
| `nodeRules[0]` vs `nodeRules[1]` (same specificity) | `nodeRules[1]` (later in array) |
|
|
173
|
+
| `nodeRules` (lower specificity) vs `childNodeRules` (higher specificity) | `childNodeRules` (higher specificity wins) |
|
|
174
|
+
| `nodeRules` (higher specificity) vs `childNodeRules` (lower specificity) | `nodeRules` (higher specificity wins) |
|
|
175
|
+
|
|
176
|
+
## Specificity
|
|
177
|
+
|
|
178
|
+
### What is Specificity?
|
|
179
|
+
|
|
180
|
+
Specificity is a three-element tuple `[a, b, c]` based on the CSS Selectors specification:
|
|
181
|
+
|
|
182
|
+
| Component | Counts | Examples |
|
|
183
|
+
| --------- | ---------------------------------------------------- | --------------------------------------------------- |
|
|
184
|
+
| `a` | ID selectors | `#main` → `[1, 0, 0]` |
|
|
185
|
+
| `b` | Class selectors, attribute selectors, pseudo-classes | `.foo` → `[0, 1, 0]`, `[type="text"]` → `[0, 1, 0]` |
|
|
186
|
+
| `c` | Type selectors, pseudo-elements | `div` → `[0, 0, 1]`, `img` → `[0, 0, 1]` |
|
|
187
|
+
|
|
188
|
+
### How Specificity is Compared
|
|
189
|
+
|
|
190
|
+
`compareSpecificity(a, b)` from `@markuplint/selector` performs a lexicographic comparison:
|
|
191
|
+
|
|
192
|
+
```typescript
|
|
193
|
+
function compareSpecificity(a: Specificity, b: Specificity): -1 | 0 | 1 {
|
|
194
|
+
// Compare a[0] vs b[0], then a[1] vs b[1], then a[2] vs b[2]
|
|
195
|
+
// Returns: -1 (a < b), 0 (equal), 1 (a > b)
|
|
196
|
+
}
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### How `RuleMapper.set()` Uses Specificity
|
|
200
|
+
|
|
201
|
+
```typescript
|
|
202
|
+
set(node, ruleName, rule: MappingLayer) {
|
|
203
|
+
const currentRule = rules[ruleName];
|
|
204
|
+
if (currentRule) {
|
|
205
|
+
const order = compareSpecificity(currentRule.specificity, rule.specificity);
|
|
206
|
+
if (order === 1) {
|
|
207
|
+
return; // Current has higher specificity → skip new rule
|
|
208
|
+
}
|
|
209
|
+
// order === 0 or -1 → overwrite with new rule
|
|
210
|
+
}
|
|
211
|
+
rules[ruleName] = rule;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
- `order === 1` (current > new): **skip** — existing higher-specificity rule is preserved
|
|
216
|
+
- `order === 0` (equal): **overwrite** — later mapping wins
|
|
217
|
+
- `order === -1` (current < new): **overwrite** — higher-specificity rule wins
|
|
218
|
+
|
|
219
|
+
### Specificity Examples
|
|
220
|
+
|
|
221
|
+
```json
|
|
222
|
+
{
|
|
223
|
+
"rules": {
|
|
224
|
+
"class-naming": "/^prefix-/"
|
|
225
|
+
},
|
|
226
|
+
"nodeRules": [
|
|
227
|
+
{
|
|
228
|
+
"selector": "div",
|
|
229
|
+
"rules": { "class-naming": "/^div-/" }
|
|
230
|
+
},
|
|
231
|
+
{
|
|
232
|
+
"selector": "div.special",
|
|
233
|
+
"rules": { "class-naming": "/^special-/" }
|
|
234
|
+
},
|
|
235
|
+
{
|
|
236
|
+
"selector": "#main",
|
|
237
|
+
"rules": { "class-naming": "/^main-/" }
|
|
238
|
+
}
|
|
239
|
+
]
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
For `<div id="main" class="special">`:
|
|
244
|
+
|
|
245
|
+
| Source | Selector | Specificity | `class-naming` value |
|
|
246
|
+
| -------------- | ------------- | ----------- | -------------------- |
|
|
247
|
+
| `rules` | (global) | `[0, 0, 0]` | `/^prefix-/` |
|
|
248
|
+
| `nodeRules[0]` | `div` | `[0, 0, 1]` | `/^div-/` |
|
|
249
|
+
| `nodeRules[1]` | `div.special` | `[0, 1, 1]` | `/^special-/` |
|
|
250
|
+
| `nodeRules[2]` | `#main` | `[1, 0, 0]` | `/^main-/` |
|
|
251
|
+
|
|
252
|
+
Processing order: `rules` → `nodeRules[0]` → `nodeRules[1]` → `nodeRules[2]`
|
|
253
|
+
|
|
254
|
+
Result: `class-naming` = `/^main-/` (specificity `[1, 0, 0]` is highest)
|
|
255
|
+
|
|
256
|
+
## Merging with Global Rules
|
|
257
|
+
|
|
258
|
+
When a `nodeRules` or `childNodeRules` entry specifies a rule, the value is **merged** with the global rule configuration rather than simply replacing it:
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
const globalRule = ruleset.rules[ruleName];
|
|
262
|
+
const mergedRule = globalRule == null ? convertedRule : mergeRule(globalRule, convertedRule);
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
`mergeRule(a, b)` (from `@markuplint/ml-config`) applies right-side precedence:
|
|
266
|
+
|
|
267
|
+
| Scenario | Result |
|
|
268
|
+
| ------------------------ | -------------------------------------------------------------------------------------------------- |
|
|
269
|
+
| `b` is `false` | Rule is disabled (returns `false`) |
|
|
270
|
+
| `b` is a primitive value | Replaces `a`'s value |
|
|
271
|
+
| Both are objects | Properties from `b` override `a`; `severity`, `value`, `options`, `reason` are individually merged |
|
|
272
|
+
| `b.options` exists | Merged with `a.options` (objects are spread, arrays are concatenated) |
|
|
273
|
+
| `b.value` is not set | Inherits from `a.value` |
|
|
274
|
+
|
|
275
|
+
### Merging Example
|
|
276
|
+
|
|
277
|
+
```json
|
|
278
|
+
{
|
|
279
|
+
"rules": {
|
|
280
|
+
"my-rule": {
|
|
281
|
+
"severity": "error",
|
|
282
|
+
"value": "strict",
|
|
283
|
+
"options": { "allow": ["a", "b"] }
|
|
284
|
+
}
|
|
285
|
+
},
|
|
286
|
+
"nodeRules": [
|
|
287
|
+
{
|
|
288
|
+
"selector": "nav",
|
|
289
|
+
"rules": {
|
|
290
|
+
"my-rule": {
|
|
291
|
+
"options": { "allow": ["c"] }
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
]
|
|
296
|
+
}
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
For `<nav>`, `my-rule` is resolved as:
|
|
300
|
+
|
|
301
|
+
```json
|
|
302
|
+
{
|
|
303
|
+
"severity": "error",
|
|
304
|
+
"value": "strict",
|
|
305
|
+
"options": { "allow": ["c"] }
|
|
306
|
+
}
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`severity` and `value` are inherited from the global config. `options.allow` is overwritten by the `nodeRules` entry (object spread, not array concatenation at the `options` level — `options` itself is spread, not individual sub-properties).
|
|
310
|
+
|
|
311
|
+
## Regex Selector and Template Variables
|
|
312
|
+
|
|
313
|
+
`nodeRules` and `childNodeRules` support regex selectors, which can capture data from element attributes. Captured values are available as Mustache template variables in the rule configuration:
|
|
314
|
+
|
|
315
|
+
```json
|
|
316
|
+
{
|
|
317
|
+
"nodeRules": [
|
|
318
|
+
{
|
|
319
|
+
"regexSelector": {
|
|
320
|
+
"attrName": "data-prefix",
|
|
321
|
+
"attrValue": "/^(?<prefix>.+)$/"
|
|
322
|
+
},
|
|
323
|
+
"rules": {
|
|
324
|
+
"class-naming": "/^{{ prefix }}-/"
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
]
|
|
328
|
+
}
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
`exchangeValueOnRule(rule, matches.data)` renders these templates before merging.
|
|
332
|
+
|
|
333
|
+
## Data Flow Diagram
|
|
334
|
+
|
|
335
|
+
```
|
|
336
|
+
Config
|
|
337
|
+
│
|
|
338
|
+
▼
|
|
339
|
+
Ruleset (rules, nodeRules, childNodeRules)
|
|
340
|
+
│
|
|
341
|
+
▼
|
|
342
|
+
_ruleMapping(ruleset)
|
|
343
|
+
│
|
|
344
|
+
├─── Layer 1: Global rules
|
|
345
|
+
│ └─ For every node (including #document):
|
|
346
|
+
│ ruleMapper.set(node, name, { from: 'rules', specificity: [0,0,0], rule })
|
|
347
|
+
│
|
|
348
|
+
├─── Layer 2: Node rules
|
|
349
|
+
│ └─ For each nodeRule entry:
|
|
350
|
+
│ └─ For each ELEMENT_NODE matching the selector:
|
|
351
|
+
│ ├─ exchangeValueOnRule (template rendering)
|
|
352
|
+
│ ├─ mergeRule (merge with global)
|
|
353
|
+
│ └─ ruleMapper.set(node, name, { from: 'nodeRules', specificity, rule })
|
|
354
|
+
│
|
|
355
|
+
└─── Layer 3: Child node rules
|
|
356
|
+
└─ For each childNodeRule entry:
|
|
357
|
+
└─ For each ELEMENT_NODE matching the selector:
|
|
358
|
+
└─ For each child (or descendant if inheritance: true):
|
|
359
|
+
├─ exchangeValueOnRule (template rendering)
|
|
360
|
+
├─ mergeRule (merge with global)
|
|
361
|
+
└─ ruleMapper.set(child, name, { from: 'childNodeRules', specificity, rule })
|
|
362
|
+
│
|
|
363
|
+
▼
|
|
364
|
+
ruleMapper.apply()
|
|
365
|
+
│
|
|
366
|
+
└─ For each node in the map:
|
|
367
|
+
node.rules[ruleName] = rule
|
|
368
|
+
│
|
|
369
|
+
▼
|
|
370
|
+
node.rules populated → ready for walkOn() verification
|
|
371
|
+
```
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# MLDOM リファレンス
|
|
2
|
+
|
|
3
|
+
`@markuplint/ml-core` の MLDOM サブシステムの詳細リファレンスです。
|
|
4
|
+
|
|
5
|
+
## ドキュメント
|
|
6
|
+
|
|
7
|
+
| ドキュメント | 説明 |
|
|
8
|
+
| -------------------------------------------------- | ------------------------------------------------------------------------------------------ |
|
|
9
|
+
| [概要](./ml-dom/overview.ja.md) | 概要、クラス階層、MLToken |
|
|
10
|
+
| [MLNode](./ml-dom/node.ja.md) | MLNode 抽象基底、MLParentNode |
|
|
11
|
+
| [MLDocument](./ml-dom/document.ja.md) | ドキュメントルートノード |
|
|
12
|
+
| [MLElement](./ml-dom/element.ja.md) | 要素ノード(セレクタマッチング、属性、省略要素) |
|
|
13
|
+
| [Pretender システム](./ml-dom/pretender.ja.md) | コンポーネントリンティング用の仮想要素マッピング(初期化、プロパティ委譲、アクセシブル名) |
|
|
14
|
+
| [MLAttr](./ml-dom/attr.ja.md) | 属性ノード(トークン分解、スプレッド属性) |
|
|
15
|
+
| [MLBlock](./ml-dom/block.ja.md) | プリプロセッサブロックノード(透過性、条件分岐子ノード、コンテンツモデル検証) |
|
|
16
|
+
| [ルールマッピング](./ml-dom/rule-mapping.ja.md) | ルールがノードに適用される仕組み(3層処理、詳細度解決) |
|
|
17
|
+
| [その他のノード型](./ml-dom/others.ja.md) | MLCharacterData, MLText, MLComment, MLDocumentType, MLElementCloseTag, MLDocumentFragment |
|
|
18
|
+
| [ヘルパーとユーティリティ](./ml-dom/helpers.ja.md) | ヘルパー関数、補助クラス、型ユーティリティ |
|
package/docs/ml-dom.md
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# MLDOM Reference
|
|
2
|
+
|
|
3
|
+
Detailed reference for the MLDOM subsystem in `@markuplint/ml-core`.
|
|
4
|
+
|
|
5
|
+
## Documents
|
|
6
|
+
|
|
7
|
+
| Document | Description |
|
|
8
|
+
| ------------------------------------------ | ---------------------------------------------------------------------------------------------------- |
|
|
9
|
+
| [Overview](./ml-dom/overview.md) | Overview, class hierarchy, MLToken |
|
|
10
|
+
| [MLNode](./ml-dom/node.md) | MLNode abstract base, MLParentNode |
|
|
11
|
+
| [MLDocument](./ml-dom/document.md) | Document root node |
|
|
12
|
+
| [MLElement](./ml-dom/element.md) | Element node (selector matching, attributes, omitted elements) |
|
|
13
|
+
| [Pretender System](./ml-dom/pretender.md) | Virtual element mapping for component linting (initialization, property delegation, accessible name) |
|
|
14
|
+
| [MLAttr](./ml-dom/attr.md) | Attribute node (token decomposition, spread attributes) |
|
|
15
|
+
| [MLBlock](./ml-dom/block.md) | Preprocessor block node (transparency, conditional child nodes, content model validation) |
|
|
16
|
+
| [Rule Mapping](./ml-dom/rule-mapping.md) | How rules are applied to nodes (three-layer processing, specificity resolution) |
|
|
17
|
+
| [Other Node Types](./ml-dom/others.md) | MLCharacterData, MLText, MLComment, MLDocumentType, MLElementCloseTag, MLDocumentFragment |
|
|
18
|
+
| [Helpers & Utilities](./ml-dom/helpers.md) | Helper functions, supplementary classes, type utilities |
|
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
# ルールシステム
|
|
2
|
+
|
|
3
|
+
`@markuplint/ml-core` のルールフレームワークの詳細リファレンスです。
|
|
4
|
+
|
|
5
|
+
## 概要
|
|
6
|
+
|
|
7
|
+
ルールフレームワークはリントルールの完全なライフサイクルを処理します:定義、設定、ノードへのマッピング、実行、違反収集。主要コンポーネント:
|
|
8
|
+
|
|
9
|
+
- **RuleSeed** — ルール定義型(verify/fix 関数 + デフォルト値)
|
|
10
|
+
- **MLRule** — ルール実行クラス(シードを名前と設定解決でラップ)
|
|
11
|
+
- **MLRuleContext** — ルールの実行コンテキスト(ドキュメントアクセス、翻訳、違反報告)
|
|
12
|
+
- **RuleMapper** — セレクタ詳細度に基づいてルール設定を特定の DOM ノードにマッピング
|
|
13
|
+
- **Ruleset** — Config から rules、nodeRules、childNodeRules を抽出
|
|
14
|
+
|
|
15
|
+
## RuleSeed
|
|
16
|
+
|
|
17
|
+
ソース: `src/ml-rule/types.ts`
|
|
18
|
+
|
|
19
|
+
`RuleSeed<T, O>` 型はルールの実装を定義します。
|
|
20
|
+
|
|
21
|
+
```typescript
|
|
22
|
+
type RuleSeed<T extends RuleConfigValue = boolean, O extends PlainData = undefined> = {
|
|
23
|
+
readonly meta?: {
|
|
24
|
+
readonly category?: 'validation' | 'style' | 'naming-convention' | 'a11y' | 'maintainability';
|
|
25
|
+
};
|
|
26
|
+
readonly defaultSeverity?: Severity;
|
|
27
|
+
readonly defaultValue?: T;
|
|
28
|
+
readonly defaultOptions?: O;
|
|
29
|
+
verify(context: ProvidedContext<T, O>): void | Promise<void>;
|
|
30
|
+
fix?(context: ProvidedContext<T, O>): void | Promise<void>;
|
|
31
|
+
};
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### カテゴリ値
|
|
35
|
+
|
|
36
|
+
| カテゴリ | 説明 |
|
|
37
|
+
| --------------------- | ------------------------------------ |
|
|
38
|
+
| `'validation'` | HTML 標準準拠チェック |
|
|
39
|
+
| `'style'` | コードスタイルとフォーマットのルール |
|
|
40
|
+
| `'naming-convention'` | 命名規則の強制 |
|
|
41
|
+
| `'a11y'` | アクセシビリティチェック |
|
|
42
|
+
| `'maintainability'` | コード保守性のルール |
|
|
43
|
+
|
|
44
|
+
### デフォルト値
|
|
45
|
+
|
|
46
|
+
- `defaultSeverity` — 未指定の場合 `'error'`
|
|
47
|
+
- `defaultValue` — 未指定の場合 `true`
|
|
48
|
+
- `defaultOptions` — 未指定の場合 `undefined`
|
|
49
|
+
|
|
50
|
+
## createRule
|
|
51
|
+
|
|
52
|
+
ソース: `src/ml-rule/create-rule.ts`
|
|
53
|
+
|
|
54
|
+
型安全なルールシード作成のためのファクトリ関数:
|
|
55
|
+
|
|
56
|
+
```typescript
|
|
57
|
+
function createRule<T extends RuleConfigValue, O extends PlainData = undefined>(
|
|
58
|
+
seed: Readonly<RuleSeed<T, O>>,
|
|
59
|
+
): RuleSeed<T, O>;
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
シードをそのまま返します。主に TypeScript の型推論のためのヘルパーとして機能します。
|
|
63
|
+
|
|
64
|
+
### 使用例
|
|
65
|
+
|
|
66
|
+
```typescript
|
|
67
|
+
import { createRule } from '@markuplint/ml-core';
|
|
68
|
+
|
|
69
|
+
export default createRule({
|
|
70
|
+
defaultSeverity: 'error',
|
|
71
|
+
defaultValue: true,
|
|
72
|
+
async verify({ document, report, t }) {
|
|
73
|
+
await document.walkOn('Element', el => {
|
|
74
|
+
if (/* 違反条件 */) {
|
|
75
|
+
report({ scope: el, message: t('エラーメッセージ') });
|
|
76
|
+
}
|
|
77
|
+
});
|
|
78
|
+
},
|
|
79
|
+
});
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## MLRule
|
|
83
|
+
|
|
84
|
+
ソース: `src/ml-rule/ml-rule.ts`
|
|
85
|
+
|
|
86
|
+
`MLRule<T, O>` は `RuleSeed` を名前でラップし、設定解決と検証実行を提供します。
|
|
87
|
+
|
|
88
|
+
### コンストラクタ
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
constructor(o: Readonly<RuleSeed<T, O>> & { readonly name: string })
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### プロパティ
|
|
95
|
+
|
|
96
|
+
| プロパティ | 型 | 説明 |
|
|
97
|
+
| ----------------- | ---------- | -------------------------------------------------- |
|
|
98
|
+
| `name` | `string` | ルール識別子(例:`"attr-duplication"`) |
|
|
99
|
+
| `defaultSeverity` | `Severity` | デフォルトの重大度レベル(シードまたは `'error'`) |
|
|
100
|
+
| `defaultValue` | `T` | デフォルト設定値(シードまたは `true`) |
|
|
101
|
+
| `defaultOptions` | `O` | デフォルトオプション(シードから) |
|
|
102
|
+
|
|
103
|
+
### メソッド
|
|
104
|
+
|
|
105
|
+
#### `verify(document, locale, fix): Promise<Violation[]>`
|
|
106
|
+
|
|
107
|
+
ドキュメントに対してルールを実行します。
|
|
108
|
+
|
|
109
|
+
**フロー:**
|
|
110
|
+
|
|
111
|
+
1. `document.setRule(this)` — ドキュメントに現在のルールコンテキストを設定
|
|
112
|
+
2. `new MLRuleContext(document, locale)` — 実行コンテキストを作成
|
|
113
|
+
3. `context.provide()` — 提供可能なコンテキストオブジェクトを生成
|
|
114
|
+
4. `await seed.verify(context)` — 検証を実行
|
|
115
|
+
5. `await seed.fix(context)` — 修正を実行(`fix=true` かつ fix 関数がある場合)
|
|
116
|
+
6. `context.reports` → `Violation[]` — レポートを違反にマッピング
|
|
117
|
+
7. `document.setRule(null)` — ルールコンテキストをクリア
|
|
118
|
+
|
|
119
|
+
**Report → Violation のマッピング:**
|
|
120
|
+
|
|
121
|
+
- スコープベースのレポート: `report.scope`(ノード)から `line`, `col`, `raw` を抽出、重大度は `report.scope.rule.severity` から
|
|
122
|
+
- 直接レポート: `report.line`, `report.col`, `report.raw` を直接使用、重大度は `document.rule.severity` から
|
|
123
|
+
|
|
124
|
+
#### `getRuleInfo(ruleSet, ruleName): GlobalRuleInfo<T, O>`
|
|
125
|
+
|
|
126
|
+
ルールセットから完全なルール情報を解決します。
|
|
127
|
+
|
|
128
|
+
戻り値:
|
|
129
|
+
|
|
130
|
+
```typescript
|
|
131
|
+
{
|
|
132
|
+
...RuleInfo<T, O>, // グローバルルール設定
|
|
133
|
+
nodeRules: RuleInfo<T, O>[], // 無効でないノードレベルのオーバーライド
|
|
134
|
+
childNodeRules: RuleInfo<T, O>[], // 無効でない子ノードレベルのオーバーライド
|
|
135
|
+
}
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
#### `optimizeOption(configSettings): RuleInfo<T, O>`
|
|
139
|
+
|
|
140
|
+
生のルール設定を解決済み `RuleInfo` に正規化します。
|
|
141
|
+
|
|
142
|
+
| 入力 | 結果 |
|
|
143
|
+
| -------------------------- | --------------------------------------------------------------------------------------------- |
|
|
144
|
+
| `undefined` または `false` | `{ disabled: true, severity: デフォルト, value: デフォルト, options: デフォルト }` |
|
|
145
|
+
| `true` | `{ disabled: false, severity: デフォルト, value: デフォルト, options: デフォルト }` |
|
|
146
|
+
| `RuleConfig` オブジェクト | `{ disabled: false, severity: 設定/デフォルト, value: 設定/デフォルト, options: マージ済み }` |
|
|
147
|
+
| プリミティブ値 | `{ disabled: false, severity: デフォルト, value: 入力値, options: デフォルト }` |
|
|
148
|
+
|
|
149
|
+
オプションのマージ: 配列はスプレッド(`[...a, ...b]`)、オブジェクトはスプレッド(`{...a, ...b}`)、それ以外は `b ?? a` にフォールバック。
|
|
150
|
+
|
|
151
|
+
## MLRuleContext
|
|
152
|
+
|
|
153
|
+
ソース: `src/ml-rule/ml-rule-context.ts`
|
|
154
|
+
|
|
155
|
+
`MLRuleContext<T, O>` はルールの実行コンテキストを提供します。
|
|
156
|
+
|
|
157
|
+
### コンストラクタ
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
constructor(document: MLDocument<T, O>, locale: LocaleSet)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
ロケールから翻訳関数を作成し、ドキュメント参照を保存します。
|
|
164
|
+
|
|
165
|
+
### プロパティ
|
|
166
|
+
|
|
167
|
+
| プロパティ | 型 | 説明 |
|
|
168
|
+
| ----------- | ------------------ | ----------------------- |
|
|
169
|
+
| `document` | `MLDocument<T, O>` | 検証対象のドキュメント |
|
|
170
|
+
| `locale` | `string` | ロケール文字列 |
|
|
171
|
+
| `translate` | `Translator` | i18n メッセージ翻訳関数 |
|
|
172
|
+
|
|
173
|
+
### `provide(): ProvidedContext`
|
|
174
|
+
|
|
175
|
+
`RuleSeed.verify()` と `RuleSeed.fix()` に渡されるコンテキストオブジェクトを返します:
|
|
176
|
+
|
|
177
|
+
```typescript
|
|
178
|
+
{
|
|
179
|
+
document: MLDocument<T, O>,
|
|
180
|
+
translate: Translator,
|
|
181
|
+
t: Translator, // translate のエイリアス
|
|
182
|
+
reports: Report<T, O>[],
|
|
183
|
+
report: (report) => void | boolean,
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
### `report(report)`
|
|
188
|
+
|
|
189
|
+
2 つのオーバーロード:
|
|
190
|
+
|
|
191
|
+
1. **直接レポート**(`Report<T, O>`): レポートを直接プッシュ。`undefined` を返す。
|
|
192
|
+
2. **チェッカーレポート**(`CheckerReport<T, O>`): 翻訳関数で関数を呼び出す。レポートが返された場合プッシュして `true` を返す。`null`/`undefined` の場合 `false` を返す。
|
|
193
|
+
|
|
194
|
+
### 重複排除
|
|
195
|
+
|
|
196
|
+
レポートは `_push()` で以下の基準で重複排除されます:
|
|
197
|
+
|
|
198
|
+
- **スコープベース**: 同じ `scope` オブジェクト + 同じ `message`
|
|
199
|
+
- **位置ベース**: 同じ `col` + `line` + `message` + `raw`
|
|
200
|
+
|
|
201
|
+
### メッセージの最終処理
|
|
202
|
+
|
|
203
|
+
英語ロケール(`'en'`)の場合、最初の小文字が大文字に変換されます。他のロケールはそのまま通過します。
|
|
204
|
+
|
|
205
|
+
## チェッカー型
|
|
206
|
+
|
|
207
|
+
ソース: `src/ml-rule/types.ts`
|
|
208
|
+
|
|
209
|
+
チェッカー関数を構築するためのユーティリティ型:
|
|
210
|
+
|
|
211
|
+
| 型 | シグネチャ | 説明 |
|
|
212
|
+
| ------------------------- | ------------------------------------------------------------ | ------------------ |
|
|
213
|
+
| `Checker<T, O, P>` | `(params: P) => CheckerReport<T, O>` | 汎用チェッカー |
|
|
214
|
+
| `ElementChecker<T, O, P>` | `(params: P & { el: Element<T, O> }) => CheckerReport<T, O>` | 要素固有チェッカー |
|
|
215
|
+
| `AttrChecker<T, O, P>` | `(params: P & { attr: Attr<T, O> }) => CheckerReport<T, O>` | 属性固有チェッカー |
|
|
216
|
+
| `CheckerReport<T, O>` | `(t: Translator) => Report<T, O> \| undefined \| null` | 遅延レポート関数 |
|
|
217
|
+
|
|
218
|
+
## ルールマッピング
|
|
219
|
+
|
|
220
|
+
`RuleMapper`、ルール設定の解決(`rules`、`nodeRules`、`childNodeRules` の3層処理)、詳細度ベースの競合解決、マージ動作、正規表現セレクタのテンプレート変数についての詳細なドキュメントは、専用の[ルールマッピング](./ml-dom/rule-mapping.ja.md)リファレンスを参照してください。
|
|
221
|
+
|
|
222
|
+
## Ruleset
|
|
223
|
+
|
|
224
|
+
ソース: `src/ruleset/index.ts`
|
|
225
|
+
|
|
226
|
+
`Config` オブジェクトからルール設定を抽出します。
|
|
227
|
+
|
|
228
|
+
```typescript
|
|
229
|
+
class Ruleset {
|
|
230
|
+
readonly rules: Rules;
|
|
231
|
+
readonly nodeRules: readonly NodeRule[];
|
|
232
|
+
readonly childNodeRules: readonly ChildNodeRule[];
|
|
233
|
+
|
|
234
|
+
constructor(config: Config);
|
|
235
|
+
}
|
|
236
|
+
```
|
|
237
|
+
|
|
238
|
+
- `rules` — グローバルルール定義(`config.rules` から、デフォルトは `{}`)
|
|
239
|
+
- `nodeRules` — ノード固有のオーバーライド(`config.nodeRules` から、デフォルトは `[]`)
|
|
240
|
+
- `childNodeRules` — 子ノード固有のオーバーライド(`config.childNodeRules` から、デフォルトは `[]`)
|
|
241
|
+
|
|
242
|
+
## テストユーティリティ
|
|
243
|
+
|
|
244
|
+
ソース: `src/ml-rule/create-test-rule.ts`
|
|
245
|
+
|
|
246
|
+
### createTestRule
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
function createRule<T, O>(seed: Readonly<RuleSeed<T, O>> & { readonly name: string }): MLRule<T, O>;
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
テスト用の `MLRule` インスタンスを作成します。`create-rule.ts` の `createRule()` とは異なり、`name` プロパティが必須で、実際の `MLRule` インスタンスを返します。
|
|
253
|
+
|
|
254
|
+
### テストパターン
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
import { createRule } from '@markuplint/ml-core/test';
|
|
258
|
+
import { createTestDocument } from '@markuplint/ml-core/test';
|
|
259
|
+
|
|
260
|
+
const rule = createRule({
|
|
261
|
+
name: 'my-rule',
|
|
262
|
+
defaultSeverity: 'error',
|
|
263
|
+
async verify({ document, report, t }) {
|
|
264
|
+
// 検証ロジック
|
|
265
|
+
},
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
const doc = createTestDocument('<div></div>');
|
|
269
|
+
const violations = await rule.verify(doc, { locale: 'en' }, false);
|
|
270
|
+
```
|