@markuplint/vue-parser 5.0.0-rc.4 → 5.0.0-rc.6
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/CHANGELOG.md +8 -0
- package/lib/component-scanner.js +0 -9
- package/lib/parser.d.ts +22 -22
- package/lib/parser.js +30 -22
- package/lib/vue-parser/index.d.ts +5 -7
- package/lib/vue-parser/index.js +5 -4
- package/package.json +6 -6
- package/ARCHITECTURE.ja.md +0 -314
- package/ARCHITECTURE.md +0 -314
- package/SKILL.md +0 -161
- package/docs/maintenance.ja.md +0 -203
- package/docs/maintenance.md +0 -203
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,14 @@
|
|
|
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-rc.6](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.5...v5.0.0-rc.6) (2026-08-30)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @markuplint/vue-parser
|
|
9
|
+
|
|
10
|
+
# [5.0.0-rc.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
|
|
11
|
+
|
|
12
|
+
**Note:** Version bump only for package @markuplint/vue-parser
|
|
13
|
+
|
|
6
14
|
# [5.0.0-rc.4](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-04-19)
|
|
7
15
|
|
|
8
16
|
**Note:** Version bump only for package @markuplint/vue-parser
|
package/lib/component-scanner.js
CHANGED
|
@@ -1,7 +1,4 @@
|
|
|
1
1
|
import { parser } from './parser.js';
|
|
2
|
-
/**
|
|
3
|
-
* Extracts root element information from a parsed MLAST document.
|
|
4
|
-
*/
|
|
5
2
|
function extractComponentInfo(doc) {
|
|
6
3
|
const root = doc.nodeList.find((n) => n.type === 'starttag' && n.depth === 0 && !n.isFragment);
|
|
7
4
|
if (!root) {
|
|
@@ -28,15 +25,9 @@ function extractComponentInfo(doc) {
|
|
|
28
25
|
col: root.col,
|
|
29
26
|
};
|
|
30
27
|
}
|
|
31
|
-
/**
|
|
32
|
-
* Detects whether the parsed Vue template contains `<slot>` elements.
|
|
33
|
-
*/
|
|
34
28
|
function detectSlots(doc) {
|
|
35
29
|
return doc.nodeList.some(n => n.type === 'starttag' && n.nodeName === 'slot');
|
|
36
30
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Extracts the `<script setup>` block from a Vue SFC source.
|
|
39
|
-
*/
|
|
40
31
|
function extractVueScriptSetup(source) {
|
|
41
32
|
const re = /<script\s[^>]*?\bsetup\b[^>]*>/i;
|
|
42
33
|
const match = re.exec(source);
|
package/lib/parser.d.ts
CHANGED
|
@@ -5,10 +5,25 @@ type State = {
|
|
|
5
5
|
comments: readonly ASTComment[];
|
|
6
6
|
};
|
|
7
7
|
/**
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
8
|
+
* Directive resolution is intentionally not performed by this parser:
|
|
9
|
+
* `directivePatterns` declared in `@markuplint/vue-spec` are applied later by
|
|
10
|
+
* `ml-core`'s `MLAttr` constructor. Consequently, parser-level output (and
|
|
11
|
+
* `index.spec.ts`) shows unresolved attribute metadata, while the final
|
|
12
|
+
* `potentialName`/`isDirective`/`isDynamicValue` values only appear at the
|
|
13
|
+
* core level.
|
|
14
|
+
*
|
|
15
|
+
* Known limitation: `v-if`/`v-for`/`v-else`/`v-else-if` do not set
|
|
16
|
+
* `blockBehavior`, so content-model rules such as `permitted-contents` cannot
|
|
17
|
+
* enumerate conditional branches via `conditionalChildNodes()` as they can for
|
|
18
|
+
* Svelte, Pug, Alpine, JSX, and Astro. Unlike Alpine's fixed
|
|
19
|
+
* `<template x-if="...">` wrapper, which converts cleanly into a PSBlock, Vue
|
|
20
|
+
* directives attach to arbitrary elements that must remain valid HTML elements
|
|
21
|
+
* for attribute validation while also acting as blocks for content-model
|
|
22
|
+
* analysis, and `v-else`/`v-else-if` branch across sibling elements — both
|
|
23
|
+
* beyond the per-node `nodeize()` model. These directives are handled at the
|
|
24
|
+
* attribute level only (`isDirective: true`), which suppresses attribute
|
|
25
|
+
* validation errors but provides no structural block information to the core
|
|
26
|
+
* engine.
|
|
12
27
|
*/
|
|
13
28
|
declare class VueParser extends Parser<ASTNode, State> {
|
|
14
29
|
constructor();
|
|
@@ -17,24 +32,11 @@ declare class VueParser extends Parser<ASTNode, State> {
|
|
|
17
32
|
readonly isFragment: boolean;
|
|
18
33
|
};
|
|
19
34
|
parseError(error: any): ParserError;
|
|
20
|
-
/**
|
|
21
|
-
* Converts a Vue AST node into markuplint node tree items.
|
|
22
|
-
* Handles VText (text nodes), VExpressionContainer (template expressions),
|
|
23
|
-
* and VElement (elements with start/end tags and children).
|
|
24
|
-
*
|
|
25
|
-
* @param originNode - The Vue AST node to convert
|
|
26
|
-
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
27
|
-
* @param depth - The nesting depth of the node
|
|
28
|
-
* @returns An array of markuplint node tree items
|
|
29
|
-
*/
|
|
30
35
|
nodeize(originNode: ASTNode, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
|
|
31
36
|
/**
|
|
32
|
-
*
|
|
33
|
-
*
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
* @param nodeTree - The hierarchical node tree to flatten
|
|
37
|
-
* @returns A flat list of nodes including interleaved comments
|
|
37
|
+
* This second pass that interleaves comments is required because
|
|
38
|
+
* vue-eslint-parser provides comments separately (`templateBody.comments`)
|
|
39
|
+
* from the main node tree, so they cannot be emitted during `nodeize()`.
|
|
38
40
|
*/
|
|
39
41
|
flattenNodes(nodeTree: readonly MLASTNodeTreeItem[]): MLASTNodeTreeItem[];
|
|
40
42
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
|
|
@@ -48,8 +50,6 @@ declare class VueParser extends Parser<ASTNode, State> {
|
|
|
48
50
|
* @see https://vuejs.org/guide/essentials/component-basics#using-a-component
|
|
49
51
|
* @see https://vuejs.org/api/built-in-components.html
|
|
50
52
|
* @see https://vuejs.org/api/built-in-special-elements.html#built-in-special-elements
|
|
51
|
-
* @param nodeName
|
|
52
|
-
* @returns
|
|
53
53
|
*/
|
|
54
54
|
detectElementType(nodeName: string): import("@markuplint/ml-ast").ElementType;
|
|
55
55
|
}
|
package/lib/parser.js
CHANGED
|
@@ -1,14 +1,31 @@
|
|
|
1
1
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
2
2
|
import { vueParse } from './vue-parser/index.js';
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* Directive resolution is intentionally not performed by this parser:
|
|
5
|
+
* `directivePatterns` declared in `@markuplint/vue-spec` are applied later by
|
|
6
|
+
* `ml-core`'s `MLAttr` constructor. Consequently, parser-level output (and
|
|
7
|
+
* `index.spec.ts`) shows unresolved attribute metadata, while the final
|
|
8
|
+
* `potentialName`/`isDirective`/`isDynamicValue` values only appear at the
|
|
9
|
+
* core level.
|
|
10
|
+
*
|
|
11
|
+
* Known limitation: `v-if`/`v-for`/`v-else`/`v-else-if` do not set
|
|
12
|
+
* `blockBehavior`, so content-model rules such as `permitted-contents` cannot
|
|
13
|
+
* enumerate conditional branches via `conditionalChildNodes()` as they can for
|
|
14
|
+
* Svelte, Pug, Alpine, JSX, and Astro. Unlike Alpine's fixed
|
|
15
|
+
* `<template x-if="...">` wrapper, which converts cleanly into a PSBlock, Vue
|
|
16
|
+
* directives attach to arbitrary elements that must remain valid HTML elements
|
|
17
|
+
* for attribute validation while also acting as blocks for content-model
|
|
18
|
+
* analysis, and `v-else`/`v-else-if` branch across sibling elements — both
|
|
19
|
+
* beyond the per-node `nodeize()` model. These directives are handled at the
|
|
20
|
+
* attribute level only (`isDirective: true`), which suppresses attribute
|
|
21
|
+
* validation errors but provides no structural block information to the core
|
|
22
|
+
* engine.
|
|
8
23
|
*/
|
|
9
24
|
class VueParser extends Parser {
|
|
10
25
|
constructor() {
|
|
11
26
|
super({
|
|
27
|
+
// Vue SFC is a compiled format that uses explicit XML-style
|
|
28
|
+
// closing tags (including `/>`), not HTML void-element rules.
|
|
12
29
|
endTagType: 'xml',
|
|
13
30
|
}, {
|
|
14
31
|
comments: [],
|
|
@@ -25,6 +42,8 @@ class VueParser extends Parser {
|
|
|
25
42
|
};
|
|
26
43
|
}
|
|
27
44
|
parseError(error) {
|
|
45
|
+
// vue-eslint-parser syntax errors carry `lineNumber` (1-based) and
|
|
46
|
+
// `column` (0-based); non-SyntaxError cases fall back to the base handler.
|
|
28
47
|
if (error instanceof SyntaxError && 'lineNumber' in error && 'column' in error) {
|
|
29
48
|
throw new ParserError(error.message, {
|
|
30
49
|
line: error.lineNumber,
|
|
@@ -34,16 +53,6 @@ class VueParser extends Parser {
|
|
|
34
53
|
}
|
|
35
54
|
return super.parseError(error);
|
|
36
55
|
}
|
|
37
|
-
/**
|
|
38
|
-
* Converts a Vue AST node into markuplint node tree items.
|
|
39
|
-
* Handles VText (text nodes), VExpressionContainer (template expressions),
|
|
40
|
-
* and VElement (elements with start/end tags and children).
|
|
41
|
-
*
|
|
42
|
-
* @param originNode - The Vue AST node to convert
|
|
43
|
-
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
44
|
-
* @param depth - The nesting depth of the node
|
|
45
|
-
* @returns An array of markuplint node tree items
|
|
46
|
-
*/
|
|
47
56
|
nodeize(
|
|
48
57
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
49
58
|
originNode, parentNode, depth) {
|
|
@@ -57,6 +66,8 @@ class VueParser extends Parser {
|
|
|
57
66
|
});
|
|
58
67
|
}
|
|
59
68
|
case 'VExpressionContainer': {
|
|
69
|
+
// Template expressions (`{{ ... }}`) are treated as opaque
|
|
70
|
+
// pseudo-blocks; their JavaScript content is intentionally not parsed.
|
|
60
71
|
return this.visitPsBlock({
|
|
61
72
|
...token,
|
|
62
73
|
depth,
|
|
@@ -89,12 +100,9 @@ class VueParser extends Parser {
|
|
|
89
100
|
}
|
|
90
101
|
}
|
|
91
102
|
/**
|
|
92
|
-
*
|
|
93
|
-
*
|
|
94
|
-
*
|
|
95
|
-
*
|
|
96
|
-
* @param nodeTree - The hierarchical node tree to flatten
|
|
97
|
-
* @returns A flat list of nodes including interleaved comments
|
|
103
|
+
* This second pass that interleaves comments is required because
|
|
104
|
+
* vue-eslint-parser provides comments separately (`templateBody.comments`)
|
|
105
|
+
* from the main node tree, so they cannot be emitted during `nodeize()`.
|
|
98
106
|
*/
|
|
99
107
|
flattenNodes(nodeTree) {
|
|
100
108
|
const nodeList = super.flattenNodes(nodeTree);
|
|
@@ -131,6 +139,8 @@ class VueParser extends Parser {
|
|
|
131
139
|
return newNodeList;
|
|
132
140
|
}
|
|
133
141
|
afterFlattenNodes(nodeList) {
|
|
142
|
+
// All base post-processing is disabled because Vue's template parser
|
|
143
|
+
// handles whitespace and node validity differently from raw HTML parsing.
|
|
134
144
|
return super.afterFlattenNodes(nodeList, {
|
|
135
145
|
exposeInvalidNode: false,
|
|
136
146
|
exposeWhiteSpace: false,
|
|
@@ -147,8 +157,6 @@ class VueParser extends Parser {
|
|
|
147
157
|
* @see https://vuejs.org/guide/essentials/component-basics#using-a-component
|
|
148
158
|
* @see https://vuejs.org/api/built-in-components.html
|
|
149
159
|
* @see https://vuejs.org/api/built-in-special-elements.html#built-in-special-elements
|
|
150
|
-
* @param nodeName
|
|
151
|
-
* @returns
|
|
152
160
|
*/
|
|
153
161
|
detectElementType(nodeName) {
|
|
154
162
|
return super.detectElementType(nodeName, [
|
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import * as VueESLintParser from 'vue-eslint-parser';
|
|
2
|
-
/** The top-level AST produced by vue-eslint-parser, containing template body and comments. */
|
|
3
2
|
export type VueTokens = VueESLintParser.AST.ESLintProgram;
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* The `parser: false` option makes vue-eslint-parser skip parsing the
|
|
5
|
+
* `<script>` block, because only the `<template>` block is relevant for
|
|
6
|
+
* markuplint. vue-eslint-parser accepts both Vue 2 and Vue 3 template
|
|
7
|
+
* syntax and produces the same node types for both, so the parser does not
|
|
8
|
+
* need to distinguish Vue versions at the AST level.
|
|
9
9
|
*/
|
|
10
10
|
export declare function vueParse(vueTemplate: string): VueTokens;
|
|
11
11
|
export type VElement = VueESLintParser.AST.VElement;
|
|
12
12
|
export type VText = VueESLintParser.AST.VText;
|
|
13
13
|
export type VExpressionContainer = VueESLintParser.AST.VExpressionContainer;
|
|
14
|
-
/** Union of AST node types that can appear as children in a Vue template. */
|
|
15
14
|
export type ASTNode = VElement | VText | VExpressionContainer;
|
|
16
|
-
/** Represents a comment token in the Vue template AST with location information. */
|
|
17
15
|
export type ASTComment = VueESLintParser.AST.Token & VueESLintParser.AST.HasLocation;
|
package/lib/vue-parser/index.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import * as VueESLintParser from 'vue-eslint-parser';
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
3
|
+
* The `parser: false` option makes vue-eslint-parser skip parsing the
|
|
4
|
+
* `<script>` block, because only the `<template>` block is relevant for
|
|
5
|
+
* markuplint. vue-eslint-parser accepts both Vue 2 and Vue 3 template
|
|
6
|
+
* syntax and produces the same node types for both, so the parser does not
|
|
7
|
+
* need to distinguish Vue versions at the AST level.
|
|
7
8
|
*/
|
|
8
9
|
export function vueParse(vueTemplate) {
|
|
9
10
|
const ast = VueESLintParser.parse(vueTemplate, { parser: false });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/vue-parser",
|
|
3
|
-
"version": "5.0.0-rc.
|
|
3
|
+
"version": "5.0.0-rc.6",
|
|
4
4
|
"description": "Vue parser for markuplint",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
11
11
|
"license": "MIT",
|
|
12
12
|
"engines": {
|
|
13
|
-
"node": ">=
|
|
13
|
+
"node": ">=24"
|
|
14
14
|
},
|
|
15
15
|
"type": "module",
|
|
16
16
|
"exports": {
|
|
@@ -32,10 +32,10 @@
|
|
|
32
32
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
|
-
"@markuplint/html-parser": "5.0.0-rc.
|
|
36
|
-
"@markuplint/ml-ast": "5.0.0-rc.
|
|
37
|
-
"@markuplint/parser-utils": "5.0.0-rc.
|
|
35
|
+
"@markuplint/html-parser": "5.0.0-rc.6",
|
|
36
|
+
"@markuplint/ml-ast": "5.0.0-rc.6",
|
|
37
|
+
"@markuplint/parser-utils": "5.0.0-rc.6",
|
|
38
38
|
"vue-eslint-parser": "10.4.0"
|
|
39
39
|
},
|
|
40
|
-
"gitHead": "
|
|
40
|
+
"gitHead": "c02c3a0783eac6b2fb4707be2dc00b88f6219641"
|
|
41
41
|
}
|
package/ARCHITECTURE.ja.md
DELETED
|
@@ -1,314 +0,0 @@
|
|
|
1
|
-
# @markuplint/vue-parser
|
|
2
|
-
|
|
3
|
-
## 概要
|
|
4
|
-
|
|
5
|
-
`@markuplint/vue-parser` は markuplint 用の Vue Single File Component(SFC)テンプレートパーサーです。vue-eslint-parser を使用して Vue SFC の `<template>` ブロックを vue-eslint-parser AST にパースし、その後統一された markuplint AST 形式(`MLASTDocument`)に変換します。Vue 固有のディレクティブ(`v-bind`、`v-on`、`v-model`、`v-slot`)、テンプレート式コンテナ(`{{ }}`)、テンプレートコメント、PascalCase コンポーネント検出を処理します。
|
|
6
|
-
|
|
7
|
-
## ディレクトリ構成
|
|
8
|
-
|
|
9
|
-
```
|
|
10
|
-
src/
|
|
11
|
-
├── index.ts — parser を再エクスポート
|
|
12
|
-
├── parser.ts — Parser<ASTNode, State> を拡張する VueParser クラス
|
|
13
|
-
├── component-scanner.ts — pretenders 自動スキャン用コンポーネントスキャナー(サブパスエクスポート)
|
|
14
|
-
├── index.spec.ts — VueParser の統合テスト
|
|
15
|
-
├── component-scanner.spec.ts — コンポーネントスキャナーのテスト
|
|
16
|
-
└── vue-parser/
|
|
17
|
-
└── index.ts — vue-eslint-parser ラッパー、ASTNode/ASTComment 型エクスポート
|
|
18
|
-
```
|
|
19
|
-
|
|
20
|
-
## アーキテクチャ図
|
|
21
|
-
|
|
22
|
-
```mermaid
|
|
23
|
-
flowchart TD
|
|
24
|
-
subgraph upstream ["上流"]
|
|
25
|
-
mlAst["@markuplint/ml-ast\n(AST 型定義)"]
|
|
26
|
-
parserUtils["@markuplint/parser-utils\n(抽象 Parser クラス)"]
|
|
27
|
-
vueEslintParser["vue-eslint-parser\n(Vue SFC トークナイザ)"]
|
|
28
|
-
end
|
|
29
|
-
|
|
30
|
-
subgraph pkg ["@markuplint/vue-parser"]
|
|
31
|
-
vueParser["VueParser\nextends Parser‹ASTNode, State›"]
|
|
32
|
-
vueParseFn["vueParse()\nvue-eslint-parser ラッパー"]
|
|
33
|
-
compScanner["componentScanner\n(サブパス: ./component-scanner)"]
|
|
34
|
-
end
|
|
35
|
-
|
|
36
|
-
subgraph downstream ["下流"]
|
|
37
|
-
mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
|
|
38
|
-
pretenders["@markuplint/pretenders\n(自動スキャン)"]
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
mlAst -->|"AST 型"| vueParser
|
|
42
|
-
parserUtils -->|"Parser 基底クラス"| vueParser
|
|
43
|
-
vueEslintParser -->|"parse()"| vueParseFn
|
|
44
|
-
vueParseFn -->|"ESLintProgram AST"| vueParser
|
|
45
|
-
|
|
46
|
-
vueParser -->|"MLASTDocument を生成"| mlCore
|
|
47
|
-
vueParser -->|"parse()"| compScanner
|
|
48
|
-
compScanner -->|"ComponentScanResult"| pretenders
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
## VueParser クラス
|
|
52
|
-
|
|
53
|
-
### 継承関係
|
|
54
|
-
|
|
55
|
-
```
|
|
56
|
-
Parser<ASTNode, State> (@markuplint/parser-utils)
|
|
57
|
-
└── VueParser (このパッケージ)
|
|
58
|
-
```
|
|
59
|
-
|
|
60
|
-
### コンストラクタ
|
|
61
|
-
|
|
62
|
-
コンストラクタは2つの引数でパーサーを構成します:
|
|
63
|
-
|
|
64
|
-
| 引数 | 値 | 用途 |
|
|
65
|
-
| --------------- | ----------------------- | ---------------------------------------------------------------------------------- |
|
|
66
|
-
| `ParserOptions` | `{ endTagType: 'xml' }` | Vue テンプレートは明示的な閉じタグを使用(XML スタイル)、HTML void ルールではない |
|
|
67
|
-
| 初期 State | `{ comments: [] }` | 空のコメント配列。`tokenize()` で設定される |
|
|
68
|
-
|
|
69
|
-
`tagNameCaseSensitive` の動作は基底クラスから継承され、Vue の `detectElementType` オーバーライドと組み合わせて PascalCase コンポーネント名を正しく処理します。
|
|
70
|
-
|
|
71
|
-
### State 型
|
|
72
|
-
|
|
73
|
-
パーサーは `State` 型を通じて内部状態を管理します:
|
|
74
|
-
|
|
75
|
-
| フィールド | 型 | 用途 |
|
|
76
|
-
| ---------- | ----------------------- | -------------------------------------------------------------------------------------------- |
|
|
77
|
-
| `comments` | `readonly ASTComment[]` | tokenize 中に vue-eslint-parser から抽出されたテンプレートコメント。後の flattenNodes で注入 |
|
|
78
|
-
|
|
79
|
-
### オーバーライドメソッド
|
|
80
|
-
|
|
81
|
-
| メソッド | 用途 |
|
|
82
|
-
| --------------------- | --------------------------------------------------------------------------------------------- |
|
|
83
|
-
| `tokenize()` | vue-eslint-parser を呼び出し、`templateBody.children` とコメントを抽出 |
|
|
84
|
-
| `parseError()` | vue-eslint-parser の `SyntaxError`(`lineNumber`/`column` 付き)を `ParserError` に変換 |
|
|
85
|
-
| `nodeize()` | vue-eslint-parser AST ノード(VText、VElement、VExpressionContainer)を markuplint AST に変換 |
|
|
86
|
-
| `flattenNodes()` | 基底のフラット化を拡張し、兄弟ノード間にテンプレートコメントを注入 |
|
|
87
|
-
| `afterFlattenNodes()` | `exposeWhiteSpace: false`、`exposeInvalidNode: false`、`concatText: false` で基底を呼び出す |
|
|
88
|
-
| `detectElementType()` | PascalCase コンポーネントと Vue 組み込みコンポーネントを検出 |
|
|
89
|
-
|
|
90
|
-
> **注記:** `visitAttr()` オーバーライドは削除されました。Vue ディレクティブ処理(`v-bind`、`v-on`、`v-model`、`v-slot` など)は `@markuplint/vue-spec` の `directivePatterns` で管理されています。
|
|
91
|
-
|
|
92
|
-
### `duplicatableAttrs`
|
|
93
|
-
|
|
94
|
-
`'class'` と `'style'` を含む `Set<string>` -- `v-bind:class` と `class` が同一要素に共存できるように、重複可能な属性を定義します。
|
|
95
|
-
|
|
96
|
-
## tokenize()
|
|
97
|
-
|
|
98
|
-
`tokenize()` メソッドは vue-eslint-parser AST を取得するエントリーポイントです:
|
|
99
|
-
|
|
100
|
-
1. `vueParse(this.rawCode)` を呼び出し、内部で `VueESLintParser.parse(vueTemplate, { parser: false })` を実行
|
|
101
|
-
2. `ast.templateBody?.comments` が存在する場合、`this.state.comments` に格納して後の注入に備える
|
|
102
|
-
3. `{ ast: ast.templateBody?.children ?? [], isFragment: true }` を返す
|
|
103
|
-
|
|
104
|
-
`parser: false` オプションは vue-eslint-parser に `<script>` のパースをスキップするよう指示します(markuplint にとって関連するのは `<template>` ブロックのみ)。ソースに `<template>` ブロックがないか空の場合、`templateBody?.children` は `undefined` を返し、パーサーは空の配列を受け取ります。
|
|
105
|
-
|
|
106
|
-
## nodeize() の詳細
|
|
107
|
-
|
|
108
|
-
`nodeize()` メソッドは `originNode.type` フィールドに基づいてディスパッチします:
|
|
109
|
-
|
|
110
|
-
### VText -> visitText
|
|
111
|
-
|
|
112
|
-
テキストノードは `this.sliceFragment(range[0], range[1])` でソースからスライスされ、depth と parentNode と共に基底の `visitText()` メソッドに渡されます。
|
|
113
|
-
|
|
114
|
-
### VExpressionContainer -> visitPsBlock
|
|
115
|
-
|
|
116
|
-
`{{ expression }}` のような式コンテナは `visitPsBlock()` で擬似ブロックノードに変換されます:
|
|
117
|
-
|
|
118
|
-
- `nodeName`: `'vue-expression-container'`
|
|
119
|
-
- `isFragment`: `false`
|
|
120
|
-
|
|
121
|
-
これにより、Vue テンプレート式は JavaScript コンテンツのパースを試みるのではなく、markuplint AST 内で不透明なブロックとして扱われます。
|
|
122
|
-
|
|
123
|
-
### VElement -> visitElement
|
|
124
|
-
|
|
125
|
-
要素ノードの場合、メソッドは:
|
|
126
|
-
|
|
127
|
-
1. `originNode.startTag.range` から**開始タグ**トークンをスライス
|
|
128
|
-
2. 要素の `name` と `namespace` と共に `visitElement()` を呼び出す
|
|
129
|
-
3. `originNode.children` を子ノードとして渡す -- テンプレートルートを表すノードの場合、これは `templateBody.children`
|
|
130
|
-
4. エンドタグトークンファクトリ(`createEndTagToken`)を作成 -- 要素が自己閉じの場合は `null` を返し、そうでなければ `originNode.endTag.range` からスライス
|
|
131
|
-
|
|
132
|
-
## flattenNodes()
|
|
133
|
-
|
|
134
|
-
`flattenNodes()` メソッドは基底の `Parser.flattenNodes()` を拡張してテンプレートコメントを注入します:
|
|
135
|
-
|
|
136
|
-
1. `super.flattenNodes(nodeTree)` を呼び出して初期フラットノードリストを取得
|
|
137
|
-
2. ノードリストを走査し、隣接するノードペア間のコメントを確認
|
|
138
|
-
3. `prevNode.endOffset`(最初のノードの場合は `parentNode.endOffset`)と `node.startOffset` の間の各ギャップについて、そのギャップ内に範囲が収まるコメントを `this.state.comments` から検索
|
|
139
|
-
4. コメントが見つかった場合、`this.visitComment()` で作成し、コメントの type に基づいて `isBogus` を設定(`HTMLBogusComment` の場合は true)
|
|
140
|
-
5. `this.appendChild()` でコメントを親ノードに追加
|
|
141
|
-
|
|
142
|
-
この2パスアプローチが必要な理由は、vue-eslint-parser がコメントをメインノードツリーとは別に提供するため、正しい位置にインターリーブする必要があるからです。
|
|
143
|
-
|
|
144
|
-
## ディレクティブ処理(@markuplint/vue-spec の directivePatterns)
|
|
145
|
-
|
|
146
|
-
Vue ディレクティブの解決はパーサー自体ではなく、`@markuplint/vue-spec` で定義された `directivePatterns` によって管理されています。spec がディレクティブを `potentialName`、`isDirective`、`isDynamicValue` メタデータにマッピングするパターンを宣言します。
|
|
147
|
-
|
|
148
|
-
> **二段階解決:** パーサーレベルのテスト(`index.spec.ts`)はパーサー自体が設定する raw AST 値を示します(例: 波括弧式のみ `isDynamicValue: true`)。コアレベルのテスト(`ml-core` や `rules`)は `ml-core` の `MLAttr` コンストラクタが `directivePatterns` を適用した後の最終解決値を示します。例えば、値なしの `on:click` はパーサーレベルでは `isDynamicValue: false` ですが、コアレベルでは `directivePatterns` マッチにより `isDynamicValue: true` に解決されます。
|
|
149
|
-
|
|
150
|
-
### クォートセット
|
|
151
|
-
|
|
152
|
-
基底パーサーは標準 HTML クォート(`"`、`'`)を処理します。Vue テンプレートは式バインディングの暗黙的な値デリミタとして `{}` も使用しますが、属性値自体は標準のクォーティングを使用します。
|
|
153
|
-
|
|
154
|
-
### Vue ディレクティブ処理
|
|
155
|
-
|
|
156
|
-
ディレクティブは優先順位に従って処理されます。最初にマッチするパターンが適用されます:
|
|
157
|
-
|
|
158
|
-
#### `v-on` / `@`(イベントバインディング)
|
|
159
|
-
|
|
160
|
-
- **パターン**: `/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i`
|
|
161
|
-
- **結果**: `potentialName: 'on' + eventName.toLowerCase()`、`isDynamicValue: true`
|
|
162
|
-
- **例**:
|
|
163
|
-
- `@click` -> `potentialName: 'onclick'`
|
|
164
|
-
- `v-on:click.stop` -> `potentialName: 'onclick'`
|
|
165
|
-
- `@keydown.enter` -> `potentialName: 'onkeydown'`
|
|
166
|
-
|
|
167
|
-
#### `v-bind` / `:`(プロパティバインディング)
|
|
168
|
-
|
|
169
|
-
- **パターン**: `/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i`
|
|
170
|
-
- **結果**(修飾子なし): `potentialName: propName`、`isDynamicValue: true`
|
|
171
|
-
- **結果**(`.attr` 修飾子): `potentialName: propName`、`isDynamicValue: true`
|
|
172
|
-
- **結果**(`.prop` / `.camel` / その他修飾子): `isDirective: true`、`potentialName` が正規化された形式に設定
|
|
173
|
-
- **`isDuplicatable`**: バインドされたプロパティが `duplicatableAttrs` に含まれる場合(class、style)、`isDuplicatable` が `true` に設定
|
|
174
|
-
- **例**:
|
|
175
|
-
- `:data-attr` -> `potentialName: 'data-attr'`
|
|
176
|
-
- `v-bind:class` -> `potentialName: 'class'`、`isDuplicatable: true`
|
|
177
|
-
- `:title.attr` -> `potentialName: 'title'`
|
|
178
|
-
- `:foo.prop` -> `isDirective: true`
|
|
179
|
-
|
|
180
|
-
#### `v-model`
|
|
181
|
-
|
|
182
|
-
- **パターン**: `/^(v-model)(?:\.([^.]+))?$/i`
|
|
183
|
-
- **結果**: `isDirective: true`
|
|
184
|
-
- **例**:
|
|
185
|
-
- `v-model` -> `isDirective: true`
|
|
186
|
-
- `v-model.lazy` -> `isDirective: true`
|
|
187
|
-
|
|
188
|
-
#### `v-slot` / `#`(スロット)
|
|
189
|
-
|
|
190
|
-
- **パターン**: `/^(v-slot:|#)(.+)$/i`
|
|
191
|
-
- **結果**: `isDirective: true`、`potentialName: 'v-slot:' + slotName`(raw name と異なる場合)
|
|
192
|
-
- **例**:
|
|
193
|
-
- `#header` -> `potentialName: 'v-slot:header'`、`isDirective: true`
|
|
194
|
-
- `v-slot:default` -> `isDirective: true`
|
|
195
|
-
|
|
196
|
-
#### その他の `v-` ディレクティブ
|
|
197
|
-
|
|
198
|
-
- **パターン**: `v-` で始まる
|
|
199
|
-
- **結果**: `isDirective: true`
|
|
200
|
-
- **例**: `v-if`、`v-for`、`v-show`、`v-else`、`v-else-if`、`v-pre`、`v-cloak`、`v-once`、`v-memo`、`v-html`、`v-text`
|
|
201
|
-
|
|
202
|
-
## 要素タイプ検出
|
|
203
|
-
|
|
204
|
-
`detectElementType()` メソッドは Vue 固有のコンポーネント検出のためのマッチャー配列を使って `super.detectElementType(nodeName, matchers)` を呼び出します:
|
|
205
|
-
|
|
206
|
-
| マッチャー | 型 | マッチ対象 |
|
|
207
|
-
| ------------------- | ------ | ----------------------------------------------- |
|
|
208
|
-
| `'Transition'` | String | Vue 組み込み `<Transition>` コンポーネント |
|
|
209
|
-
| `'TransitionGroup'` | String | Vue 組み込み `<TransitionGroup>` コンポーネント |
|
|
210
|
-
| `'KeepAlive'` | String | Vue 組み込み `<KeepAlive>` コンポーネント |
|
|
211
|
-
| `'Teleport'` | String | Vue 組み込み `<Teleport>` コンポーネント |
|
|
212
|
-
| `'Suspense'` | String | Vue 組み込み `<Suspense>` コンポーネント |
|
|
213
|
-
| `'component'` | String | Vue 特殊要素 `<component :is="...">` |
|
|
214
|
-
| `'slot'` | String | Vue 特殊要素 `<slot>` |
|
|
215
|
-
| `/^[A-Z]/` | RegExp | PascalCase のタグ名(ユーザーコンポーネント) |
|
|
216
|
-
|
|
217
|
-
タグ名がこれらのいずれかにマッチする場合、`detectElementType()` は `'authored'`(コンポーネントを示す)を返します。それ以外は標準の HTML 要素検出が適用されます:
|
|
218
|
-
|
|
219
|
-
- `div`、`span`、`p` 等 -> `'html'`
|
|
220
|
-
- `x-foo`、`my-element` -> `'web-component'`
|
|
221
|
-
|
|
222
|
-
`<transition>`(小文字)は組み込みリストにマッチ**しない**ため標準 HTML 要素(`'html'`)として扱われますが、`<Transition>`(PascalCase)は `'authored'` として扱われることに注意してください。
|
|
223
|
-
|
|
224
|
-
## afterFlattenNodes()
|
|
225
|
-
|
|
226
|
-
`afterFlattenNodes()` メソッドは特定のオプションで基底実装を呼び出します:
|
|
227
|
-
|
|
228
|
-
| オプション | 値 | 効果 |
|
|
229
|
-
| ------------------- | ------- | -------------------------------------------------------- |
|
|
230
|
-
| `exposeWhiteSpace` | `false` | 空白のみのテキストノードは別の無効ノードとして公開しない |
|
|
231
|
-
| `exposeInvalidNode` | `false` | 無効なノードは公開しない |
|
|
232
|
-
| `concatText` | `false` | 隣接するテキストノードは結合しない |
|
|
233
|
-
|
|
234
|
-
これらの設定は、Vue のテンプレートパーサーが空白やノードの妥当性を生の HTML パースとは異なる方法で処理することを反映しています。
|
|
235
|
-
|
|
236
|
-
## バージョン互換性
|
|
237
|
-
|
|
238
|
-
vue-eslint-parser 依存は Vue 2 と Vue 3 の両方のテンプレート構文をサポートしています。パーサーは AST レベルで Vue バージョンを区別しません -- どちらも同じ `VElement`、`VText`、`VExpressionContainer` ノードタイプを生成します。Vue 3 固有の機能(`<Teleport>` や `<Suspense>` など)はパーサーレベルの変更ではなく、要素タイプ検出を通じて処理されます。
|
|
239
|
-
|
|
240
|
-
## 制約事項
|
|
241
|
-
|
|
242
|
-
### `v-if` / `v-for` の `blockBehavior` 未対応
|
|
243
|
-
|
|
244
|
-
他のフレームワークパーサー(Svelte、Pug、Alpine、JSX、Astro)は条件分岐/ループ構文に `blockBehavior` を設定し、コアエンジンが `conditionalChildNodes()` を通じて全てのありうる子ノードパターンを列挙できるようにしています。Vue パーサーはこれを**サポートしていません**。そのため、`permitted-contents` などのルールは Vue テンプレートの `v-if`/`v-else` 分岐や `v-for` イテレーション全体のコンテンツモデル検証ができません。
|
|
245
|
-
|
|
246
|
-
**実装が困難な理由:**
|
|
247
|
-
|
|
248
|
-
Alpine.js では条件分岐とループに決まったパターン — `<template x-for="...">` / `<template x-if="...">` — を使用しており、`<template>` 要素をそのまま PSBlock に変換できます。Vue のディレクティブは根本的に異なる仕組みで動作します:
|
|
249
|
-
|
|
250
|
-
1. **ディレクティブは任意の要素に付与可能**: `v-if`、`v-for`、`v-else`、`v-else-if` は任意の要素に配置できます(例: `<div v-if="...">`、`<li v-for="...">`)。要素は属性検証のために有効な HTML 要素として残しつつ、コンテンツモデル解析のためにブロックとしても機能させる必要があります — この二重の役割は現在のパーサーアーキテクチャではサポートされていません。
|
|
251
|
-
|
|
252
|
-
2. **兄弟要素ベースの分岐**: `v-else` と `v-else-if` はラッパーブロックの子構造ではなく、**兄弟**要素の属性です。条件グループの構築には、現在のノード単位の `nodeize()` モデルを超えた兄弟間解析が必要です。
|
|
253
|
-
|
|
254
|
-
現在の Vue パーサーはこれらのディレクティブを属性レベルでのみ処理しており(`isDirective: true`)、属性検証エラーは抑制されますが、構造的なブロック情報はコアエンジンに提供されません。
|
|
255
|
-
|
|
256
|
-
## 主要ソースファイル
|
|
257
|
-
|
|
258
|
-
| ファイル | 用途 |
|
|
259
|
-
| -------------------------- | ------------------------------------------------------------------------------------------------------------- |
|
|
260
|
-
| `src/parser.ts` | 全オーバーライドメソッドを持つ VueParser クラス |
|
|
261
|
-
| `src/vue-parser/index.ts` | vue-eslint-parser ラッパーと型定義(ASTNode、ASTComment) |
|
|
262
|
-
| `src/index.ts` | モジュールエントリーポイント、parser インスタンスを再エクスポート |
|
|
263
|
-
| `src/index.spec.ts` | パース、ディレクティブ、名前空間をカバーする統合テスト |
|
|
264
|
-
| `src/component-scanner.ts` | `@markuplint/pretenders` 自動スキャン用コンポーネントスキャナー(サブパスエクスポート `./component-scanner`) |
|
|
265
|
-
|
|
266
|
-
## 外部依存
|
|
267
|
-
|
|
268
|
-
| 依存パッケージ | 用途 |
|
|
269
|
-
| -------------------------- | ---------------------------------------------------------------- |
|
|
270
|
-
| `@markuplint/ml-ast` | AST 型定義(`MLASTParentNode`、`MLASTNodeTreeItem` 等) |
|
|
271
|
-
| `@markuplint/parser-utils` | 抽象 `Parser` クラス、`ParserError`、`Token`、`ChildToken` |
|
|
272
|
-
| `@markuplint/html-parser` | ピア依存(直接インポートされないが、パーサーエコシステムの一部) |
|
|
273
|
-
| `vue-eslint-parser` | Vue SFC テンプレートパース(`parse`、AST 型) |
|
|
274
|
-
|
|
275
|
-
## 統合ポイント
|
|
276
|
-
|
|
277
|
-
```mermaid
|
|
278
|
-
flowchart TD
|
|
279
|
-
subgraph upstream ["上流"]
|
|
280
|
-
mlAst["@markuplint/ml-ast\n(AST 型定義)"]
|
|
281
|
-
parserUtils["@markuplint/parser-utils\n(Parser 基底クラス)"]
|
|
282
|
-
vueEslintParser["vue-eslint-parser\n(Vue SFC トークナイザ)"]
|
|
283
|
-
end
|
|
284
|
-
|
|
285
|
-
subgraph pkg ["@markuplint/vue-parser"]
|
|
286
|
-
vueParser["VueParser"]
|
|
287
|
-
compScanner["componentScanner\n(./component-scanner)"]
|
|
288
|
-
end
|
|
289
|
-
|
|
290
|
-
subgraph downstream ["下流"]
|
|
291
|
-
mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
|
|
292
|
-
pretenders["@markuplint/pretenders\n(自動スキャン)"]
|
|
293
|
-
end
|
|
294
|
-
|
|
295
|
-
upstream -->|"型、パース"| vueParser
|
|
296
|
-
vueParser -->|"MLASTDocument を生成"| mlCore
|
|
297
|
-
vueParser -->|"parse()"| compScanner
|
|
298
|
-
compScanner -->|"ComponentScanResult"| pretenders
|
|
299
|
-
```
|
|
300
|
-
|
|
301
|
-
### 上流
|
|
302
|
-
|
|
303
|
-
- **`@markuplint/ml-ast`** -- パーサー全体で使用される AST 型定義
|
|
304
|
-
- **`@markuplint/parser-utils`** -- `VueParser` が拡張する抽象 `Parser` クラスと `ParserError` およびユーティリティ型
|
|
305
|
-
- **`vue-eslint-parser`** -- テンプレートのトークン化とツリー構築を行う基盤 Vue SFC パーサー
|
|
306
|
-
|
|
307
|
-
### 下流
|
|
308
|
-
|
|
309
|
-
- **`@markuplint/ml-core`** -- `VueParser` が生成する `MLASTDocument` を消費し、ルール評価のための MLDOM を構築
|
|
310
|
-
- **`@markuplint/pretenders`** -- `./component-scanner` を動的インポートし、ルート要素・属性・スロット情報を抽出して自動スキャンに利用
|
|
311
|
-
|
|
312
|
-
## ドキュメントマップ
|
|
313
|
-
|
|
314
|
-
- [メンテナンスガイド](docs/maintenance.ja.md) -- コマンド、レシピ、トラブルシューティング
|