@markuplint/vue-parser 4.6.23 → 5.0.0-alpha.1
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 +22 -3
- package/ARCHITECTURE.md +22 -3
- package/CHANGELOG.md +20 -0
- package/SKILL.md +9 -9
- package/docs/maintenance.ja.md +3 -3
- package/docs/maintenance.md +3 -3
- package/lib/parser.d.ts +2 -17
- package/lib/parser.js +6 -106
- package/lib/vue-parser/index.d.ts +4 -1
- package/package.json +9 -6
package/ARCHITECTURE.ja.md
CHANGED
|
@@ -79,9 +79,10 @@ Parser<ASTNode, State> (@markuplint/parser-utils)
|
|
|
79
79
|
| `nodeize()` | vue-eslint-parser AST ノード(VText、VElement、VExpressionContainer)を markuplint AST に変換 |
|
|
80
80
|
| `flattenNodes()` | 基底のフラット化を拡張し、兄弟ノード間にテンプレートコメントを注入 |
|
|
81
81
|
| `afterFlattenNodes()` | `exposeWhiteSpace: false`、`exposeInvalidNode: false`、`concatText: false` で基底を呼び出す |
|
|
82
|
-
| `visitAttr()` | Vue ディレクティブの省略形を `potentialName`/`isDirective` メタデータに解決 |
|
|
83
82
|
| `detectElementType()` | PascalCase コンポーネントと Vue 組み込みコンポーネントを検出 |
|
|
84
83
|
|
|
84
|
+
> **注記:** `visitAttr()` オーバーライドは削除されました。Vue ディレクティブ処理(`v-bind`、`v-on`、`v-model`、`v-slot` など)は `@markuplint/vue-spec` の `directivePatterns` で管理されています。
|
|
85
|
+
|
|
85
86
|
### `duplicatableAttrs`
|
|
86
87
|
|
|
87
88
|
`'class'` と `'style'` を含む `Set<string>` -- `v-bind:class` と `class` が同一要素に共存できるように、重複可能な属性を定義します。
|
|
@@ -134,9 +135,11 @@ Parser<ASTNode, State> (@markuplint/parser-utils)
|
|
|
134
135
|
|
|
135
136
|
この2パスアプローチが必要な理由は、vue-eslint-parser がコメントをメインノードツリーとは別に提供するため、正しい位置にインターリーブする必要があるからです。
|
|
136
137
|
|
|
137
|
-
##
|
|
138
|
+
## ディレクティブ処理(@markuplint/vue-spec の directivePatterns)
|
|
139
|
+
|
|
140
|
+
Vue ディレクティブの解決はパーサー自体ではなく、`@markuplint/vue-spec` で定義された `directivePatterns` によって管理されています。spec がディレクティブを `potentialName`、`isDirective`、`isDynamicValue` メタデータにマッピングするパターンを宣言します。
|
|
138
141
|
|
|
139
|
-
`
|
|
142
|
+
> **二段階解決:** パーサーレベルのテスト(`index.spec.ts`)はパーサー自体が設定する raw AST 値を示します(例: 波括弧式のみ `isDynamicValue: true`)。コアレベルのテスト(`ml-core` や `rules`)は `ml-core` の `MLAttr` コンストラクタが `directivePatterns` を適用した後の最終解決値を示します。例えば、値なしの `on:click` はパーサーレベルでは `isDynamicValue: false` ですが、コアレベルでは `directivePatterns` マッチにより `isDynamicValue: true` に解決されます。
|
|
140
143
|
|
|
141
144
|
### クォートセット
|
|
142
145
|
|
|
@@ -228,6 +231,22 @@ Parser<ASTNode, State> (@markuplint/parser-utils)
|
|
|
228
231
|
|
|
229
232
|
vue-eslint-parser 依存は Vue 2 と Vue 3 の両方のテンプレート構文をサポートしています。パーサーは AST レベルで Vue バージョンを区別しません -- どちらも同じ `VElement`、`VText`、`VExpressionContainer` ノードタイプを生成します。Vue 3 固有の機能(`<Teleport>` や `<Suspense>` など)はパーサーレベルの変更ではなく、要素タイプ検出を通じて処理されます。
|
|
230
233
|
|
|
234
|
+
## 制約事項
|
|
235
|
+
|
|
236
|
+
### `v-if` / `v-for` の `blockBehavior` 未対応
|
|
237
|
+
|
|
238
|
+
他のフレームワークパーサー(Svelte、Pug、Alpine、JSX、Astro)は条件分岐/ループ構文に `blockBehavior` を設定し、コアエンジンが `conditionalChildNodes()` を通じて全てのありうる子ノードパターンを列挙できるようにしています。Vue パーサーはこれを**サポートしていません**。そのため、`permitted-contents` などのルールは Vue テンプレートの `v-if`/`v-else` 分岐や `v-for` イテレーション全体のコンテンツモデル検証ができません。
|
|
239
|
+
|
|
240
|
+
**実装が困難な理由:**
|
|
241
|
+
|
|
242
|
+
Alpine.js では条件分岐とループに決まったパターン — `<template x-for="...">` / `<template x-if="...">` — を使用しており、`<template>` 要素をそのまま PSBlock に変換できます。Vue のディレクティブは根本的に異なる仕組みで動作します:
|
|
243
|
+
|
|
244
|
+
1. **ディレクティブは任意の要素に付与可能**: `v-if`、`v-for`、`v-else`、`v-else-if` は任意の要素に配置できます(例: `<div v-if="...">`、`<li v-for="...">`)。要素は属性検証のために有効な HTML 要素として残しつつ、コンテンツモデル解析のためにブロックとしても機能させる必要があります — この二重の役割は現在のパーサーアーキテクチャではサポートされていません。
|
|
245
|
+
|
|
246
|
+
2. **兄弟要素ベースの分岐**: `v-else` と `v-else-if` はラッパーブロックの子構造ではなく、**兄弟**要素の属性です。条件グループの構築には、現在のノード単位の `nodeize()` モデルを超えた兄弟間解析が必要です。
|
|
247
|
+
|
|
248
|
+
現在の Vue パーサーはこれらのディレクティブを属性レベルでのみ処理しており(`isDirective: true`)、属性検証エラーは抑制されますが、構造的なブロック情報はコアエンジンに提供されません。
|
|
249
|
+
|
|
231
250
|
## 主要ソースファイル
|
|
232
251
|
|
|
233
252
|
| ファイル | 用途 |
|
package/ARCHITECTURE.md
CHANGED
|
@@ -79,9 +79,10 @@ The parser maintains internal state through the `State` type:
|
|
|
79
79
|
| `nodeize()` | Converts vue-eslint-parser AST nodes (VText, VElement, VExpressionContainer) to markuplint AST |
|
|
80
80
|
| `flattenNodes()` | Extends base flattening to inject template comments between sibling nodes |
|
|
81
81
|
| `afterFlattenNodes()` | Calls base with `exposeWhiteSpace: false`, `exposeInvalidNode: false`, `concatText: false` |
|
|
82
|
-
| `visitAttr()` | Resolves Vue directive shorthands to `potentialName`/`isDirective` metadata |
|
|
83
82
|
| `detectElementType()` | Detects PascalCase components and Vue built-in components |
|
|
84
83
|
|
|
84
|
+
> **Note:** The `visitAttr()` override has been removed. Vue directive handling (`v-bind`, `v-on`, `v-model`, `v-slot`, etc.) is now managed via `directivePatterns` in `@markuplint/vue-spec`.
|
|
85
|
+
|
|
85
86
|
### `duplicatableAttrs`
|
|
86
87
|
|
|
87
88
|
A `Set<string>` containing `'class'` and `'style'` -- attributes that may appear multiple times on a single element (via `v-bind:class` alongside `class`).
|
|
@@ -134,9 +135,11 @@ The `flattenNodes()` method extends the base `Parser.flattenNodes()` to inject t
|
|
|
134
135
|
|
|
135
136
|
This two-pass approach is necessary because vue-eslint-parser provides comments separately from the main node tree, and they must be interleaved at the correct positions.
|
|
136
137
|
|
|
137
|
-
##
|
|
138
|
+
## Directive Handling (directivePatterns in @markuplint/vue-spec)
|
|
139
|
+
|
|
140
|
+
Vue directive resolution is managed by `directivePatterns` defined in `@markuplint/vue-spec`, not by the parser itself. The spec declares patterns that the core engine uses to map directives to `potentialName`, `isDirective`, and `isDynamicValue` metadata.
|
|
138
141
|
|
|
139
|
-
|
|
142
|
+
> **Two-stage resolution:** Parser-level tests (`index.spec.ts`) show raw AST values where `isDynamicValue` and `isDirective` reflect only what the parser itself sets (e.g., curly-brace expressions). Core-level tests (`ml-core` and `rules`) show the final resolved values after `directivePatterns` are applied by `ml-core`'s `MLAttr` constructor. For example, `on:click` without a value shows `isDynamicValue: false` at the parser level, but resolves to `isDynamicValue: true` at the core level via the `directivePatterns` match.
|
|
140
143
|
|
|
141
144
|
### Quote Set
|
|
142
145
|
|
|
@@ -228,6 +231,22 @@ These settings reflect that Vue's template parser handles whitespace and node va
|
|
|
228
231
|
|
|
229
232
|
The vue-eslint-parser dependency supports both Vue 2 and Vue 3 template syntax. The parser does not distinguish between Vue versions at the AST level -- both produce the same `VElement`, `VText`, and `VExpressionContainer` node types. Vue 3-specific features like `<Teleport>` and `<Suspense>` are handled through element type detection rather than parser-level changes.
|
|
230
233
|
|
|
234
|
+
## Limitations
|
|
235
|
+
|
|
236
|
+
### No `blockBehavior` support for `v-if` / `v-for`
|
|
237
|
+
|
|
238
|
+
Other framework parsers (Svelte, Pug, Alpine, JSX, Astro) set `blockBehavior` on their conditional/loop constructs so that the core engine can enumerate all possible child node patterns via `conditionalChildNodes()`. The Vue parser does **not** support this. As a result, rules like `permitted-contents` cannot validate content models across `v-if`/`v-else` branches or `v-for` iterations in Vue templates.
|
|
239
|
+
|
|
240
|
+
**Why this is difficult to implement:**
|
|
241
|
+
|
|
242
|
+
In Alpine.js, conditionals and loops use a fixed pattern — `<template x-for="...">` / `<template x-if="...">` — where the `<template>` element can be cleanly converted into a PSBlock. Vue's directives work fundamentally differently:
|
|
243
|
+
|
|
244
|
+
1. **Directives attach to arbitrary elements**: `v-if`, `v-for`, `v-else`, and `v-else-if` can appear on any element (e.g., `<div v-if="...">`, `<li v-for="...">`). The element must remain a valid HTML element for attribute validation while simultaneously acting as a block for content model analysis — a dual role the current parser architecture does not support.
|
|
245
|
+
|
|
246
|
+
2. **Sibling-based branching**: `v-else` and `v-else-if` are attributes on **sibling** elements, not child constructs of a wrapper block. Building conditional groups requires cross-sibling analysis that goes beyond the current per-node `nodeize()` model.
|
|
247
|
+
|
|
248
|
+
The current Vue parser handles these directives at the attribute level only (`isDirective: true`), which suppresses attribute validation errors but does not provide structural block information to the core engine.
|
|
249
|
+
|
|
231
250
|
## Key Source Files
|
|
232
251
|
|
|
233
252
|
| File | Purpose |
|
package/CHANGELOG.md
CHANGED
|
@@ -3,6 +3,26 @@
|
|
|
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.1](https://github.com/markuplint/markuplint/compare/v5.0.0-alpha.0...v5.0.0-alpha.1) (2026-02-22)
|
|
7
|
+
|
|
8
|
+
**Note:** Version bump only for package @markuplint/vue-parser
|
|
9
|
+
|
|
10
|
+
# [5.0.0-alpha.0](https://github.com/markuplint/markuplint/compare/v4.14.1...v5.0.0-alpha.0) (2026-02-20)
|
|
11
|
+
|
|
12
|
+
### Bug Fixes
|
|
13
|
+
|
|
14
|
+
- **ml-core:** improve detection of namespace ([5b507ad](https://github.com/markuplint/markuplint/commit/5b507ad7c19c5015b8ce587845d901e31dfa6518))
|
|
15
|
+
- **vue-parser:** update vue-eslint-parser to 10.3.0 and fix TS4053 errors ([b4633ea](https://github.com/markuplint/markuplint/commit/b4633eaeeb55c3b969127071094fde8e51bfb451))
|
|
16
|
+
|
|
17
|
+
- refactor(vue-parser)!: update for simplified AST token properties ([b7e52df](https://github.com/markuplint/markuplint/commit/b7e52df21b6af0a4f2b61b327e60ed609f4359cc))
|
|
18
|
+
|
|
19
|
+
### BREAKING CHANGES
|
|
20
|
+
|
|
21
|
+
- Replace startOffset/endOffset with offset and
|
|
22
|
+
offset + raw.length in flattenNodes comment handling.
|
|
23
|
+
|
|
24
|
+
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
|
25
|
+
|
|
6
26
|
## [4.6.23](https://github.com/markuplint/markuplint/compare/@markuplint/vue-parser@4.6.22...@markuplint/vue-parser@4.6.23) (2026-02-10)
|
|
7
27
|
|
|
8
28
|
**Note:** Version bump only for package @markuplint/vue-parser
|
package/SKILL.md
CHANGED
|
@@ -14,12 +14,12 @@ detection, update vue-eslint-parser version support, and fix comment injection.
|
|
|
14
14
|
|
|
15
15
|
`$ARGUMENTS` specifies the task. Supported tasks:
|
|
16
16
|
|
|
17
|
-
| Task | Description
|
|
18
|
-
| ------------------------------- |
|
|
19
|
-
| `add-directive` | Add or modify a Vue directive in
|
|
20
|
-
| `modify-element-type-detection` | Update detectElementType() matchers
|
|
21
|
-
| `update-vue-version-support` | Handle vue-eslint-parser API changes
|
|
22
|
-
| `fix-comment-injection` | Fix template comment injection in flattenNodes()
|
|
17
|
+
| Task | Description |
|
|
18
|
+
| ------------------------------- | ------------------------------------------------------------- |
|
|
19
|
+
| `add-directive` | Add or modify a Vue directive in directivePatterns (vue-spec) |
|
|
20
|
+
| `modify-element-type-detection` | Update detectElementType() matchers |
|
|
21
|
+
| `update-vue-version-support` | Handle vue-eslint-parser API changes |
|
|
22
|
+
| `fix-comment-injection` | Fix template comment injection in flattenNodes() |
|
|
23
23
|
|
|
24
24
|
If omitted, defaults to `add-directive`.
|
|
25
25
|
|
|
@@ -35,11 +35,11 @@ Also read:
|
|
|
35
35
|
|
|
36
36
|
## Task: add-directive
|
|
37
37
|
|
|
38
|
-
Add or modify a Vue directive in `
|
|
38
|
+
Add or modify a Vue directive in `directivePatterns` (`@markuplint/vue-spec`). Follow recipe #1 in `docs/maintenance.md`.
|
|
39
39
|
|
|
40
40
|
### Step 1: Understand the directive
|
|
41
41
|
|
|
42
|
-
1.
|
|
42
|
+
1. Open `@markuplint/vue-spec` (`packages/@markuplint/vue-spec/src/index.ts`) and find the `directivePatterns` array
|
|
43
43
|
2. Identify where in the priority chain the new directive should be processed
|
|
44
44
|
3. Determine whether the directive needs `potentialName`, `isDirective`, or `isDynamicValue`
|
|
45
45
|
|
|
@@ -134,4 +134,4 @@ Fix template comment injection in `flattenNodes()`. Follow recipe #4 in `docs/ma
|
|
|
134
134
|
3. **Use `isDirective: true`** for directives with no HTML equivalent (e.g., `v-if`, `v-for`).
|
|
135
135
|
4. **Test with `nodeListToDebugMaps`** — this is the standard assertion pattern for parser tests.
|
|
136
136
|
5. **Add JSDoc comments** to all new public methods and properties.
|
|
137
|
-
6. **Preserve directive priority order** in `
|
|
137
|
+
6. **Preserve directive priority order** in `directivePatterns` — `.prop` shorthand before `v-bind` with modifiers before `v-bind`/`:` before `v-on`/`@` before `v-model` before `v-slot`/`#` before generic `v-`.
|
package/docs/maintenance.ja.md
CHANGED
|
@@ -49,7 +49,7 @@ expect(doc.nodeList[0].elementType).toBe('authored');
|
|
|
49
49
|
|
|
50
50
|
### 1. Vue ディレクティブの追加・変更
|
|
51
51
|
|
|
52
|
-
1.
|
|
52
|
+
1. `@markuplint/vue-spec`(`packages/@markuplint/vue-spec/src/index.ts`)を開き、`directivePatterns` 配列を確認する
|
|
53
53
|
2. 優先チェーン内の正しい位置を特定:
|
|
54
54
|
- `v-on` / `@`(イベントバインディング) — 最初
|
|
55
55
|
- `v-bind` / `:`(プロパティバインディング) — 2番目
|
|
@@ -158,12 +158,12 @@ yarn test --scope @markuplint/vue-parser
|
|
|
158
158
|
|
|
159
159
|
**症状:** `v-custom` のような Vue ディレクティブが `isDirective: true` としてマークされず、通常の HTML 属性として扱われる。
|
|
160
160
|
|
|
161
|
-
**原因:** `
|
|
161
|
+
**原因:** `directivePatterns`(`@markuplint/vue-spec` で定義)でディレクティブパターンがマッチしないか、新しいパターンが汎用 `v-*` キャッチオールの後に配置されている。
|
|
162
162
|
|
|
163
163
|
**解決策:**
|
|
164
164
|
|
|
165
165
|
1. ディレクティブブロックの正規表現パターンを確認 — 完全な属性名にマッチすることを確認
|
|
166
|
-
2.
|
|
166
|
+
2. パターンが `directivePatterns` 配列内の汎用 `v-*` キャッチオールの前に配置されていることを確認
|
|
167
167
|
3. 返却オブジェクトに `isDirective: true as const` が含まれていることを検証
|
|
168
168
|
|
|
169
169
|
### テンプレートコメントが AST に含まれていない
|
package/docs/maintenance.md
CHANGED
|
@@ -49,7 +49,7 @@ expect(doc.nodeList[0].elementType).toBe('authored');
|
|
|
49
49
|
|
|
50
50
|
### 1. Adding or Modifying a Vue Directive
|
|
51
51
|
|
|
52
|
-
1.
|
|
52
|
+
1. Open `@markuplint/vue-spec` (`packages/@markuplint/vue-spec/src/index.ts`) and find the `directivePatterns` array
|
|
53
53
|
2. Identify the correct position in the priority chain:
|
|
54
54
|
- `v-on` / `@` (event binding) — first
|
|
55
55
|
- `v-bind` / `:` (property binding) — second
|
|
@@ -158,12 +158,12 @@ yarn test --scope @markuplint/vue-parser
|
|
|
158
158
|
|
|
159
159
|
**Symptom:** A Vue directive like `v-custom` is treated as a regular HTML attribute instead of being marked as `isDirective: true`.
|
|
160
160
|
|
|
161
|
-
**Cause:** The directive pattern does not match in `
|
|
161
|
+
**Cause:** The directive pattern does not match in `directivePatterns` (defined in `@markuplint/vue-spec`), or the new pattern is placed after the generic `v-*` catch-all.
|
|
162
162
|
|
|
163
163
|
**Solution:**
|
|
164
164
|
|
|
165
165
|
1. Check the regex pattern in your directive block — ensure it matches the full attribute name
|
|
166
|
-
2. Ensure the
|
|
166
|
+
2. Ensure the pattern is placed before the generic `v-*` catch-all in the `directivePatterns` array
|
|
167
167
|
3. Verify the return object includes `isDirective: true as const`
|
|
168
168
|
|
|
169
169
|
### Template comments are missing from the AST
|
package/lib/parser.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import type { ASTNode, ASTComment } from './vue-parser/index.js';
|
|
2
2
|
import type { MLASTParentNode, MLASTNodeTreeItem } from '@markuplint/ml-ast';
|
|
3
|
-
import type { Token } from '@markuplint/parser-utils';
|
|
4
3
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
5
4
|
type State = {
|
|
6
5
|
comments: readonly ASTComment[];
|
|
@@ -12,11 +11,10 @@ type State = {
|
|
|
12
11
|
* Recognizes Vue built-in components and PascalCase user components.
|
|
13
12
|
*/
|
|
14
13
|
declare class VueParser extends Parser<ASTNode, State> {
|
|
15
|
-
readonly duplicatableAttrs: Set<string>;
|
|
16
14
|
constructor();
|
|
17
15
|
tokenize(): {
|
|
18
|
-
ast:
|
|
19
|
-
isFragment: boolean;
|
|
16
|
+
readonly ast: ASTNode[];
|
|
17
|
+
readonly isFragment: boolean;
|
|
20
18
|
};
|
|
21
19
|
parseError(error: any): ParserError;
|
|
22
20
|
/**
|
|
@@ -40,19 +38,6 @@ declare class VueParser extends Parser<ASTNode, State> {
|
|
|
40
38
|
*/
|
|
41
39
|
flattenNodes(nodeTree: readonly MLASTNodeTreeItem[]): MLASTNodeTreeItem[];
|
|
42
40
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
|
|
43
|
-
/**
|
|
44
|
-
* Visits an attribute token and resolves Vue-specific directive shorthands.
|
|
45
|
-
* Handles `v-on` / `@` (event binding), `v-bind` / `:` (property binding),
|
|
46
|
-
* `v-model`, `v-slot` / `#`, and other `v-` prefixed directives.
|
|
47
|
-
*
|
|
48
|
-
* @param token - The token representing the attribute
|
|
49
|
-
* @returns The parsed attribute node with Vue-specific metadata
|
|
50
|
-
*/
|
|
51
|
-
visitAttr(token: Token): (import("@markuplint/ml-ast").MLASTHTMLAttr & {
|
|
52
|
-
__rightText?: string;
|
|
53
|
-
}) | (import("@markuplint/ml-ast").MLASTSpreadAttr & {
|
|
54
|
-
__rightText?: string;
|
|
55
|
-
});
|
|
56
41
|
/**
|
|
57
42
|
* > In SFCs, it's recommended to use `PascalCase` tag names
|
|
58
43
|
* > for child components to differentiate from native HTML elements.
|
package/lib/parser.js
CHANGED
|
@@ -13,7 +13,6 @@ class VueParser extends Parser {
|
|
|
13
13
|
}, {
|
|
14
14
|
comments: [],
|
|
15
15
|
});
|
|
16
|
-
this.duplicatableAttrs = new Set(['class', 'style']);
|
|
17
16
|
}
|
|
18
17
|
tokenize() {
|
|
19
18
|
const ast = vueParse(this.rawCode);
|
|
@@ -73,7 +72,6 @@ class VueParser extends Parser {
|
|
|
73
72
|
depth,
|
|
74
73
|
parentNode,
|
|
75
74
|
nodeName: originNode.name,
|
|
76
|
-
namespace: originNode.namespace,
|
|
77
75
|
}, originNode.children, {
|
|
78
76
|
createEndTagToken: () => {
|
|
79
77
|
if (!originNode.endTag) {
|
|
@@ -103,9 +101,13 @@ class VueParser extends Parser {
|
|
|
103
101
|
const newNodeList = [];
|
|
104
102
|
let prevNode = null;
|
|
105
103
|
for (const node of nodeList) {
|
|
106
|
-
const lastOffset = prevNode
|
|
104
|
+
const lastOffset = prevNode
|
|
105
|
+
? prevNode.offset + prevNode.raw.length
|
|
106
|
+
: node.parentNode
|
|
107
|
+
? node.parentNode.offset + node.parentNode.raw.length
|
|
108
|
+
: 0;
|
|
107
109
|
const betweenComment = this.state.comments.find(comment => {
|
|
108
|
-
return lastOffset <= comment.range[0] && comment.range[1] <= node.
|
|
110
|
+
return lastOffset <= comment.range[0] && comment.range[1] <= node.offset;
|
|
109
111
|
});
|
|
110
112
|
if (betweenComment) {
|
|
111
113
|
const token = this.sliceFragment(betweenComment.range[0], betweenComment.range[1]);
|
|
@@ -135,108 +137,6 @@ class VueParser extends Parser {
|
|
|
135
137
|
concatText: false,
|
|
136
138
|
});
|
|
137
139
|
}
|
|
138
|
-
/**
|
|
139
|
-
* Visits an attribute token and resolves Vue-specific directive shorthands.
|
|
140
|
-
* Handles `v-on` / `@` (event binding), `v-bind` / `:` (property binding),
|
|
141
|
-
* `v-model`, `v-slot` / `#`, and other `v-` prefixed directives.
|
|
142
|
-
*
|
|
143
|
-
* @param token - The token representing the attribute
|
|
144
|
-
* @returns The parsed attribute node with Vue-specific metadata
|
|
145
|
-
*/
|
|
146
|
-
visitAttr(token) {
|
|
147
|
-
const attr = super.visitAttr(token);
|
|
148
|
-
if (attr.type === 'spread') {
|
|
149
|
-
return attr;
|
|
150
|
-
}
|
|
151
|
-
{
|
|
152
|
-
/**
|
|
153
|
-
* `v-on`
|
|
154
|
-
*/
|
|
155
|
-
const [, directive, potentialName] = attr.name.raw.match(/^(v-on:|@)([^.]+)(?:\.([^.]+))?$/i) ?? [];
|
|
156
|
-
if (directive && potentialName) {
|
|
157
|
-
return {
|
|
158
|
-
...attr,
|
|
159
|
-
potentialName: `on${potentialName.toLowerCase()}`,
|
|
160
|
-
isDynamicValue: true,
|
|
161
|
-
};
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
{
|
|
165
|
-
/**
|
|
166
|
-
* `v-bind`
|
|
167
|
-
*/
|
|
168
|
-
const [, directive, potentialName, modifier] = attr.name.raw.match(/^(v-bind:|:)([^.]+)(?:\.([^.]+))?$/i) ?? [];
|
|
169
|
-
if (directive && potentialName) {
|
|
170
|
-
if (this.duplicatableAttrs.has(potentialName.toLowerCase())) {
|
|
171
|
-
this.updateAttr(attr, { isDuplicatable: true });
|
|
172
|
-
}
|
|
173
|
-
if (!modifier) {
|
|
174
|
-
return {
|
|
175
|
-
...attr,
|
|
176
|
-
potentialName,
|
|
177
|
-
isDynamicValue: true,
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
switch (modifier) {
|
|
181
|
-
case '.attr': {
|
|
182
|
-
return {
|
|
183
|
-
...attr,
|
|
184
|
-
potentialName,
|
|
185
|
-
isDynamicValue: true,
|
|
186
|
-
};
|
|
187
|
-
}
|
|
188
|
-
/* eslint-disable unicorn/no-useless-switch-case */
|
|
189
|
-
case '.prop':
|
|
190
|
-
case '.camel':
|
|
191
|
-
default: {
|
|
192
|
-
const name = `v-bind:${potentialName}${modifier ?? ''}`;
|
|
193
|
-
return {
|
|
194
|
-
...attr,
|
|
195
|
-
potentialName: attr.name.raw === name ? undefined : name,
|
|
196
|
-
isDirective: true,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
/* eslint-enable unicorn/no-useless-switch-case */
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
{
|
|
204
|
-
/**
|
|
205
|
-
* `v-model`
|
|
206
|
-
*/
|
|
207
|
-
const [, directive] = attr.name.raw.match(/^(v-model)(?:\.([^.]+))?$/i) ?? [];
|
|
208
|
-
if (directive) {
|
|
209
|
-
return {
|
|
210
|
-
...attr,
|
|
211
|
-
isDirective: true,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
{
|
|
216
|
-
/**
|
|
217
|
-
* `v-slot`
|
|
218
|
-
*/
|
|
219
|
-
const slotName = (attr.name.raw.match(/^(v-slot:|#)(.+)$/i) ?? [])[2];
|
|
220
|
-
const name = `v-slot:${slotName}`;
|
|
221
|
-
if (slotName) {
|
|
222
|
-
return {
|
|
223
|
-
...attr,
|
|
224
|
-
potentialName: attr.name.raw === name ? undefined : name,
|
|
225
|
-
isDirective: true,
|
|
226
|
-
};
|
|
227
|
-
}
|
|
228
|
-
}
|
|
229
|
-
/**
|
|
230
|
-
* If directives
|
|
231
|
-
*/
|
|
232
|
-
if (attr.name.raw.startsWith('v-')) {
|
|
233
|
-
return {
|
|
234
|
-
...attr,
|
|
235
|
-
isDirective: true,
|
|
236
|
-
};
|
|
237
|
-
}
|
|
238
|
-
return attr;
|
|
239
|
-
}
|
|
240
140
|
/**
|
|
241
141
|
* > In SFCs, it's recommended to use `PascalCase` tag names
|
|
242
142
|
* > for child components to differentiate from native HTML elements.
|
|
@@ -8,7 +8,10 @@ export type VueTokens = VueESLintParser.AST.ESLintProgram;
|
|
|
8
8
|
* @returns The parsed AST program node containing the template body
|
|
9
9
|
*/
|
|
10
10
|
export declare function vueParse(vueTemplate: string): VueTokens;
|
|
11
|
+
export type VElement = VueESLintParser.AST.VElement;
|
|
12
|
+
export type VText = VueESLintParser.AST.VText;
|
|
13
|
+
export type VExpressionContainer = VueESLintParser.AST.VExpressionContainer;
|
|
11
14
|
/** Union of AST node types that can appear as children in a Vue template. */
|
|
12
|
-
export type ASTNode =
|
|
15
|
+
export type ASTNode = VElement | VText | VExpressionContainer;
|
|
13
16
|
/** Represents a comment token in the Vue template AST with location information. */
|
|
14
17
|
export type ASTComment = VueESLintParser.AST.Token & VueESLintParser.AST.HasLocation;
|
package/package.json
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@markuplint/vue-parser",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.0-alpha.1",
|
|
4
4
|
"description": "Vue parser for markuplint",
|
|
5
5
|
"repository": "git@github.com:markuplint/markuplint.git",
|
|
6
6
|
"author": "Yusuke Hirao <yusukehirao@me.com>",
|
|
7
7
|
"license": "MIT",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=22"
|
|
10
|
+
},
|
|
8
11
|
"type": "module",
|
|
9
12
|
"exports": {
|
|
10
13
|
".": {
|
|
@@ -21,10 +24,10 @@
|
|
|
21
24
|
"clean": "tsc --build --clean tsconfig.build.json"
|
|
22
25
|
},
|
|
23
26
|
"dependencies": {
|
|
24
|
-
"@markuplint/html-parser": "
|
|
25
|
-
"@markuplint/ml-ast": "
|
|
26
|
-
"@markuplint/parser-utils": "
|
|
27
|
-
"vue-eslint-parser": "10.
|
|
27
|
+
"@markuplint/html-parser": "5.0.0-alpha.1",
|
|
28
|
+
"@markuplint/ml-ast": "5.0.0-alpha.1",
|
|
29
|
+
"@markuplint/parser-utils": "5.0.0-alpha.1",
|
|
30
|
+
"vue-eslint-parser": "10.4.0"
|
|
28
31
|
},
|
|
29
|
-
"gitHead": "
|
|
32
|
+
"gitHead": "78a295e73a097a1ce09c777c06fa21ab68136387"
|
|
30
33
|
}
|