@markuplint/pug-parser 4.6.22 → 4.18.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/ARCHITECTURE.ja.md +436 -0
- package/ARCHITECTURE.md +436 -0
- package/CHANGELOG.md +9 -1
- package/SKILL.md +118 -0
- package/docs/maintenance.ja.md +188 -0
- package/docs/maintenance.md +188 -0
- package/lib/index.d.ts +6 -0
- package/lib/index.js +6 -0
- package/lib/parser.d.ts +38 -0
- package/lib/parser.js +43 -0
- package/lib/pug-parser/index.d.ts +9 -0
- package/lib/pug-parser/index.js +99 -3
- package/lib/types.d.ts +29 -0
- package/lib/utils/get-offset-from-line-and-col.d.ts +10 -0
- package/lib/utils/get-offset-from-line-and-col.js +10 -0
- package/package.json +5 -5
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# メンテナンスガイド
|
|
2
|
+
|
|
3
|
+
## コマンド
|
|
4
|
+
|
|
5
|
+
| コマンド | 説明 |
|
|
6
|
+
| ------------------------------------------- | ---------------------- |
|
|
7
|
+
| `yarn build --scope @markuplint/pug-parser` | このパッケージをビルド |
|
|
8
|
+
| `yarn dev --scope @markuplint/pug-parser` | ウォッチモードでビルド |
|
|
9
|
+
| `yarn clean --scope @markuplint/pug-parser` | ビルド成果物を削除 |
|
|
10
|
+
| `yarn test --scope @markuplint/pug-parser` | テストを実行 |
|
|
11
|
+
|
|
12
|
+
## テスト
|
|
13
|
+
|
|
14
|
+
テストファイルは `*.spec.ts` の命名規則に従います:
|
|
15
|
+
|
|
16
|
+
| テストファイル | カバレッジ |
|
|
17
|
+
| ------------------------------------------------ | ---------------------------------------------------------------- |
|
|
18
|
+
| `src/index.spec.ts` | PugParser 統合テスト(Pug テンプレートのエンドツーエンドパース) |
|
|
19
|
+
| `src/pug-parser/index.spec.ts` | AST 最適化テスト(pugParse、optimizeAST) |
|
|
20
|
+
| `src/utils/get-offset-from-line-and-col.spec.ts` | オフセット計算ユーティリティのテスト |
|
|
21
|
+
|
|
22
|
+
主なテストパターンでは `nodeListToDebugMaps` を使用したスナップショット形式のアサーションを行います:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { nodeListToDebugMaps } from '@markuplint/parser-utils';
|
|
26
|
+
import { parser } from '@markuplint/pug-parser';
|
|
27
|
+
|
|
28
|
+
const doc = parser.parse('div.foo#bar text content');
|
|
29
|
+
const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
|
|
30
|
+
expect(debugMaps).toStrictEqual([
|
|
31
|
+
// 期待されるデバッグ出力
|
|
32
|
+
]);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## レシピ
|
|
36
|
+
|
|
37
|
+
### 1. 新しい Pug AST ノードタイプの追加
|
|
38
|
+
|
|
39
|
+
1. `src/types.ts` を読み、新しい最適化済み型を追加:
|
|
40
|
+
```ts
|
|
41
|
+
export type ASTNewType = PugAST.NewType & AdditionalASTData;
|
|
42
|
+
```
|
|
43
|
+
2. 新しい型を `ASTNode` 共用体に追加
|
|
44
|
+
3. `src/pug-parser/index.ts` を読み、`optimizeAST()` に新しい `case` を追加:
|
|
45
|
+
```ts
|
|
46
|
+
case 'NewType': {
|
|
47
|
+
const block = optimizeAST(node.block, tokens, pug);
|
|
48
|
+
const newNode: ASTNewType = {
|
|
49
|
+
type: node.type,
|
|
50
|
+
raw,
|
|
51
|
+
offset,
|
|
52
|
+
endOffset,
|
|
53
|
+
line,
|
|
54
|
+
endLine,
|
|
55
|
+
column,
|
|
56
|
+
endColumn,
|
|
57
|
+
block,
|
|
58
|
+
filename: node.filename ?? null,
|
|
59
|
+
};
|
|
60
|
+
nodes.push(newNode);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
4. `src/parser.ts` を読み、`nodeize()` に新しい `case` を追加:
|
|
65
|
+
- HTML 的な要素の場合: `getNamespace()` と属性で `visitElement()` を使用
|
|
66
|
+
- Pug 固有の構文の場合: 子ノードで `visitPsBlock()` を使用
|
|
67
|
+
5. ビルド: `yarn build --scope @markuplint/pug-parser`
|
|
68
|
+
6. テスト: `yarn test --scope @markuplint/pug-parser`
|
|
69
|
+
|
|
70
|
+
### 2. 属性処理の変更
|
|
71
|
+
|
|
72
|
+
1. `src/parser.ts` を読む — `visitAttr()` メソッドには3つのパスがある:
|
|
73
|
+
- **ショートハンド**(`#` / `.`): `AttrState.BeforeValue` を使用し、`potentialName` を設定
|
|
74
|
+
- **通常**: `noQuoteValueType: 'script'` を使用
|
|
75
|
+
- **値パース**: `scriptParser()` で型検出(Numeric、Boolean、String、Template、dynamic)
|
|
76
|
+
2. 変更を行う:
|
|
77
|
+
- 新しいショートハンド構文の場合: 既存の `#`/`.` チェックの前に条件を追加
|
|
78
|
+
- 属性名変換の場合: `attr.name.raw.endsWith('!')` チェックの後に追加
|
|
79
|
+
- 値型検出の場合: `scriptParser()` 結果の switch を変更
|
|
80
|
+
3. `this.updateAttr()` でメタデータを設定: `potentialName`、`potentialValue`、`isDuplicatable`、`valueType`
|
|
81
|
+
4. ビルドとテスト: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
82
|
+
|
|
83
|
+
### 3. AST 最適化の更新
|
|
84
|
+
|
|
85
|
+
1. `src/pug-parser/index.ts` を読む — 最適化パイプライン:
|
|
86
|
+
- `pugParse()` — エントリーポイント: lex → parse → optimize
|
|
87
|
+
- `optimizeAST()` — 再帰的なノード強化
|
|
88
|
+
- `getOffsetsFromLines()` — 累積オフセットルックアップテーブル
|
|
89
|
+
- `getLocationFromToken()` — 行/列によるトークンマッチング
|
|
90
|
+
- `getAttrs()` — レキサートークンからの属性強化
|
|
91
|
+
- `getEndAttributeLocation()` — 属性を含むタグの終了位置
|
|
92
|
+
- `mergeTextNode()` — 連続テキストノードのマージ
|
|
93
|
+
- `getPipelessText()` — パイプレステキストブロックの検出
|
|
94
|
+
- `getRawTextAndLocationEnd()` — 複数行テキストの処理
|
|
95
|
+
- `optimizeASTOfConditionalNode()` — else-if/else チェーンの処理
|
|
96
|
+
2. 変更時の注意点:
|
|
97
|
+
- オフセットは `getOffsetsFromLines()` から `offsets[line - 2]` を使用して計算する必要がある
|
|
98
|
+
- 終了位置は `getLocationFromToken()` でマッチするレキサートークンから取得
|
|
99
|
+
- `raw` は元のソースからスライスする必要がある: `pug.slice(offset, endOffset)`
|
|
100
|
+
- `structuredClone()` によるトークンのクローンが必要 — パーサーはトークン配列を変更する
|
|
101
|
+
3. ビルドとテスト: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
102
|
+
|
|
103
|
+
### 4. インライン HTML 処理の変更
|
|
104
|
+
|
|
105
|
+
1. `src/parser.ts` を読む — `nodeize()` の `Text` ケース:
|
|
106
|
+
- `<` や `#[` を含むテキストは `HtmlInPugParser` でパースされる
|
|
107
|
+
- `#ps:tag-interpolation` ノードは `PugParser` で再帰的にパースされる
|
|
108
|
+
2. タグ補間構文を変更するには:
|
|
109
|
+
- `HtmlInPugParser` コンストラクタの `ignoreTags` を変更
|
|
110
|
+
- `Text` ケースの `#ps:tag-interpolation` 検出を更新
|
|
111
|
+
- `#[` プレフィックスと `]` サフィックスの除去のオフセット計算を更新
|
|
112
|
+
3. インライン HTML の動作を変更するには:
|
|
113
|
+
- `HtmlInPugParser` クラスを変更(`HtmlParser` を拡張)
|
|
114
|
+
- `offsetOffset`、`offsetLine`、`offsetColumn` コンテキストが正しく渡される必要がある
|
|
115
|
+
4. ビルドとテスト: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
116
|
+
|
|
117
|
+
### 5. useOffset インデントフィルタリングの変更
|
|
118
|
+
|
|
119
|
+
1. `src/pug-parser/index.ts` を読む — `pugParse()` 関数
|
|
120
|
+
2. `useOffset` フラグは、ゼロ以外のオフセットでサブテンプレートをパースする際に `indent` と `outdent` トークンをフィルタリングする
|
|
121
|
+
3. 変更するには:
|
|
122
|
+
- `if (useOffset)` ブロック内のフィルタ条件を変更
|
|
123
|
+
- サブテンプレートコンテキストで他のトークンタイプもフィルタリングが必要かを検討
|
|
124
|
+
4. ビルドとテスト: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
125
|
+
|
|
126
|
+
## トラブルシューティング
|
|
127
|
+
|
|
128
|
+
### Pug AST ノードのオフセットがおかしい
|
|
129
|
+
|
|
130
|
+
**症状:** Pug ノードの `offset`、`endOffset`、`endLine`、`endColumn` が markuplint AST 内で不正。
|
|
131
|
+
|
|
132
|
+
**原因:** `optimizeAST()` 関数がオフセットを誤って計算しているか、マッチするレキサートークンが不正。
|
|
133
|
+
|
|
134
|
+
**解決策:**
|
|
135
|
+
|
|
136
|
+
1. `getOffsetsFromLines()` を確認 — 入力に対して累積オフセットテーブルが正しいことを検証
|
|
137
|
+
2. `getLocationFromToken()` を確認 — 行/列で正しいトークンがマッチし、`tokenType` フィルタリングが正しいことを確認
|
|
138
|
+
3. 属性付きタグの場合、`getEndAttributeLocation()` を確認 — 正しいトークンで停止していることを検証
|
|
139
|
+
4. デバッグログを追加: 関連する `optimizeAST()` ケースで `console.log(JSON.stringify(node, null, 2))`
|
|
140
|
+
|
|
141
|
+
### タグ補間がパースされない
|
|
142
|
+
|
|
143
|
+
**症状:** `#[tag content]` が markuplint ノードに展開されず、生テキストとして表示される。
|
|
144
|
+
|
|
145
|
+
**原因:** テキストノードが `HtmlInPugParser` パスに到達していないか、`#ps:tag-interpolation` の検出が失敗している。
|
|
146
|
+
|
|
147
|
+
**解決策:**
|
|
148
|
+
|
|
149
|
+
1. `nodeize()` の `Text` ケースを確認 — `originNode.raw.includes('#[')` が `true` と評価されることを確認
|
|
150
|
+
2. `HtmlInPugParser` を確認 — `ignoreTags` が `#[...]` を正しくマスクしていることを検証
|
|
151
|
+
3. `#ps:tag-interpolation` の検出を確認 — `node.nodeName === '#ps:tag-interpolation'` であることを確認
|
|
152
|
+
4. 再帰的な `PugParser` 呼び出しを確認 — オフセットコンテキスト(`offsetOffset`、`offsetLine`、`offsetColumn`)が正しく計算されていることを検証
|
|
153
|
+
|
|
154
|
+
### 複雑な式の属性パースが失敗する
|
|
155
|
+
|
|
156
|
+
**症状:** 複雑な JavaScript 値を持つ Pug 属性(例: `data-value=obj.prop + 1`)がパースエラーまたは不正な AST を引き起こす。
|
|
157
|
+
|
|
158
|
+
**原因:** 複数トークンの式に対して `scriptParser()` の結果が正しく処理されていない。
|
|
159
|
+
|
|
160
|
+
**解決策:**
|
|
161
|
+
|
|
162
|
+
1. `visitAttr()` メソッドを確認 — 最後の `return` ブロックが複数トークンの式を `isDynamicValue: true, valueType: 'code'` として処理している
|
|
163
|
+
2. `@markuplint/parser-utils` の `scriptParser()` を確認 — 値の式が正しくトークン化されていることを検証
|
|
164
|
+
3. 単一トークンの式の場合、`switch (token.type)` がトークンタイプを正しく処理しているか検証
|
|
165
|
+
|
|
166
|
+
### ショートハンド属性の potentialName がおかしい
|
|
167
|
+
|
|
168
|
+
**症状:** `#my-id` や `.my-class` が間違った `potentialName` を持つ属性を生成する。
|
|
169
|
+
|
|
170
|
+
**原因:** ショートハンド検出または `endOffset` の再計算が誤っている。
|
|
171
|
+
|
|
172
|
+
**解決策:**
|
|
173
|
+
|
|
174
|
+
1. `visitAttr()` の `#`/`.` ブランチを確認 — `potentialName` が `#` の場合 `'id'`、`.` の場合 `'class'` に設定されていることを確認
|
|
175
|
+
2. `nodeize()` の Tag ケースで `endOffset` の再計算を確認 — ショートハンド属性では `attr.offset === attr.endOffset` が true であり、`endOffset` は `attr.offset + attr.val.length - 1` であるべき
|
|
176
|
+
3. Pug AST の `val` 値を検証 — `#my-id` の場合、`val` は `"'my-id'"`(前後にクォート付き)であるべき
|
|
177
|
+
|
|
178
|
+
### パイプレステキストが検出されない
|
|
179
|
+
|
|
180
|
+
**症状:** `.` 付きタグの後のインデントされたテキストがパイプレステキストとして扱われない。
|
|
181
|
+
|
|
182
|
+
**原因:** `getPipelessText()` 関数が `start-pipeless-text` / `end-pipeless-text` トークンを見つけられないか、行範囲チェックが失敗している。
|
|
183
|
+
|
|
184
|
+
**解決策:**
|
|
185
|
+
|
|
186
|
+
1. レキサー出力を確認 — `start-pipeless-text` と `end-pipeless-text` トークンが存在することを検証
|
|
187
|
+
2. 行範囲を確認: `startPipelessText.loc.start.line < node.line && node.line < endPipelessText.loc.start.line`
|
|
188
|
+
3. トークンが存在するが範囲が誤っている場合、pug-lexer バージョンの問題の可能性がある
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# Maintenance Guide
|
|
2
|
+
|
|
3
|
+
## Commands
|
|
4
|
+
|
|
5
|
+
| Command | Description |
|
|
6
|
+
| ------------------------------------------- | ---------------------- |
|
|
7
|
+
| `yarn build --scope @markuplint/pug-parser` | Build this package |
|
|
8
|
+
| `yarn dev --scope @markuplint/pug-parser` | Watch mode build |
|
|
9
|
+
| `yarn clean --scope @markuplint/pug-parser` | Remove build artifacts |
|
|
10
|
+
| `yarn test --scope @markuplint/pug-parser` | Run tests |
|
|
11
|
+
|
|
12
|
+
## Testing
|
|
13
|
+
|
|
14
|
+
Test files follow the `*.spec.ts` naming convention:
|
|
15
|
+
|
|
16
|
+
| Test File | Coverage |
|
|
17
|
+
| ------------------------------------------------ | -------------------------------------------------------------- |
|
|
18
|
+
| `src/index.spec.ts` | PugParser integration tests (parsing Pug templates end-to-end) |
|
|
19
|
+
| `src/pug-parser/index.spec.ts` | AST optimization tests (pugParse, optimizeAST) |
|
|
20
|
+
| `src/utils/get-offset-from-line-and-col.spec.ts` | Offset calculation utility tests |
|
|
21
|
+
|
|
22
|
+
The primary testing pattern uses `nodeListToDebugMaps` for snapshot-style assertions:
|
|
23
|
+
|
|
24
|
+
```ts
|
|
25
|
+
import { nodeListToDebugMaps } from '@markuplint/parser-utils';
|
|
26
|
+
import { parser } from '@markuplint/pug-parser';
|
|
27
|
+
|
|
28
|
+
const doc = parser.parse('div.foo#bar text content');
|
|
29
|
+
const debugMaps = nodeListToDebugMaps(doc.nodeList, true);
|
|
30
|
+
expect(debugMaps).toStrictEqual([
|
|
31
|
+
// expected debug output
|
|
32
|
+
]);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## Recipes
|
|
36
|
+
|
|
37
|
+
### 1. Adding a New Pug AST Node Type
|
|
38
|
+
|
|
39
|
+
1. Read `src/types.ts` — add the new optimized type:
|
|
40
|
+
```ts
|
|
41
|
+
export type ASTNewType = PugAST.NewType & AdditionalASTData;
|
|
42
|
+
```
|
|
43
|
+
2. Add the new type to the `ASTNode` union
|
|
44
|
+
3. Read `src/pug-parser/index.ts` — add a new `case` in `optimizeAST()`:
|
|
45
|
+
```ts
|
|
46
|
+
case 'NewType': {
|
|
47
|
+
const block = optimizeAST(node.block, tokens, pug);
|
|
48
|
+
const newNode: ASTNewType = {
|
|
49
|
+
type: node.type,
|
|
50
|
+
raw,
|
|
51
|
+
offset,
|
|
52
|
+
endOffset,
|
|
53
|
+
line,
|
|
54
|
+
endLine,
|
|
55
|
+
column,
|
|
56
|
+
endColumn,
|
|
57
|
+
block,
|
|
58
|
+
filename: node.filename ?? null,
|
|
59
|
+
};
|
|
60
|
+
nodes.push(newNode);
|
|
61
|
+
continue;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
4. Read `src/parser.ts` — add a new `case` in `nodeize()`:
|
|
65
|
+
- For HTML-like elements: use `visitElement()` with `getNamespace()` and attributes
|
|
66
|
+
- For Pug-specific constructs: use `visitPsBlock()` with child nodes
|
|
67
|
+
5. Build: `yarn build --scope @markuplint/pug-parser`
|
|
68
|
+
6. Test: `yarn test --scope @markuplint/pug-parser`
|
|
69
|
+
|
|
70
|
+
### 2. Modifying Attribute Processing
|
|
71
|
+
|
|
72
|
+
1. Read `src/parser.ts` — the `visitAttr()` method has three paths:
|
|
73
|
+
- **Shorthand** (`#` / `.`): Uses `AttrState.BeforeValue`, sets `potentialName`
|
|
74
|
+
- **Regular**: Uses `noQuoteValueType: 'script'`
|
|
75
|
+
- **Value parsing**: Uses `scriptParser()` for type detection (Numeric, Boolean, String, Template, dynamic)
|
|
76
|
+
2. Make the change:
|
|
77
|
+
- For new shorthand syntax: add a condition before the existing `#`/`.` check
|
|
78
|
+
- For attribute name transforms: add after the `attr.name.raw.endsWith('!')` check
|
|
79
|
+
- For value type detection: modify the `scriptParser()` result switch
|
|
80
|
+
3. Use `this.updateAttr()` to set metadata: `potentialName`, `potentialValue`, `isDuplicatable`, `valueType`
|
|
81
|
+
4. Build and test: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
82
|
+
|
|
83
|
+
### 3. Updating AST Optimization
|
|
84
|
+
|
|
85
|
+
1. Read `src/pug-parser/index.ts` — the optimization pipeline:
|
|
86
|
+
- `pugParse()` — entry point: lex → parse → optimize
|
|
87
|
+
- `optimizeAST()` — recursive node enrichment
|
|
88
|
+
- `getOffsetsFromLines()` — cumulative offset lookup table
|
|
89
|
+
- `getLocationFromToken()` — token matching by line/column
|
|
90
|
+
- `getAttrs()` — attribute enrichment from lexer tokens
|
|
91
|
+
- `getEndAttributeLocation()` — tag end position including attributes
|
|
92
|
+
- `mergeTextNode()` — consecutive text node merging
|
|
93
|
+
- `getPipelessText()` — pipeless text block detection
|
|
94
|
+
- `getRawTextAndLocationEnd()` — multi-line text handling
|
|
95
|
+
- `optimizeASTOfConditionalNode()` — else-if/else chain processing
|
|
96
|
+
2. Make the change, paying attention to:
|
|
97
|
+
- Offsets must be computed from `getOffsetsFromLines()` using `offsets[line - 2]`
|
|
98
|
+
- End positions come from matching lexer tokens via `getLocationFromToken()`
|
|
99
|
+
- `raw` must be sliced from the original source: `pug.slice(offset, endOffset)`
|
|
100
|
+
- Token cloning via `structuredClone()` is required — the parser mutates the token array
|
|
101
|
+
3. Build and test: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
102
|
+
|
|
103
|
+
### 4. Modifying Inline HTML Handling
|
|
104
|
+
|
|
105
|
+
1. Read `src/parser.ts` — the `Text` case in `nodeize()`:
|
|
106
|
+
- Text with `<` or `#[` is parsed through `HtmlInPugParser`
|
|
107
|
+
- `#ps:tag-interpolation` nodes are recursively parsed by `PugParser`
|
|
108
|
+
2. To change tag interpolation syntax:
|
|
109
|
+
- Modify the `ignoreTags` in `HtmlInPugParser` constructor
|
|
110
|
+
- Update the `#ps:tag-interpolation` detection in the `Text` case
|
|
111
|
+
- Update the offset calculations for `#[` prefix and `]` suffix stripping
|
|
112
|
+
3. To change inline HTML behavior:
|
|
113
|
+
- Modify the `HtmlInPugParser` class (extends `HtmlParser`)
|
|
114
|
+
- The `offsetOffset`, `offsetLine`, `offsetColumn` context must be passed correctly
|
|
115
|
+
4. Build and test: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
116
|
+
|
|
117
|
+
### 5. Modifying the useOffset Indent Filtering
|
|
118
|
+
|
|
119
|
+
1. Read `src/pug-parser/index.ts` — the `pugParse()` function
|
|
120
|
+
2. The `useOffset` flag filters `indent` and `outdent` tokens when parsing sub-templates at non-zero offsets
|
|
121
|
+
3. To modify:
|
|
122
|
+
- Change the filter condition in the `if (useOffset)` block
|
|
123
|
+
- Consider whether other token types need filtering for the sub-template context
|
|
124
|
+
4. Build and test: `yarn build --scope @markuplint/pug-parser && yarn test --scope @markuplint/pug-parser`
|
|
125
|
+
|
|
126
|
+
## Troubleshooting
|
|
127
|
+
|
|
128
|
+
### Pug AST node has wrong offsets
|
|
129
|
+
|
|
130
|
+
**Symptom:** A Pug node's `offset`, `endOffset`, `endLine`, or `endColumn` is incorrect in the markuplint AST.
|
|
131
|
+
|
|
132
|
+
**Cause:** The `optimizeAST()` function is computing wrong offsets, or the matching lexer token is incorrect.
|
|
133
|
+
|
|
134
|
+
**Solution:**
|
|
135
|
+
|
|
136
|
+
1. Check `getOffsetsFromLines()` — verify the cumulative offset table is correct for the input
|
|
137
|
+
2. Check `getLocationFromToken()` — ensure the correct token is matched by line/column, and that `tokenType` filtering is correct
|
|
138
|
+
3. For tags with attributes, check `getEndAttributeLocation()` — verify it stops at the right token
|
|
139
|
+
4. Add debug logging: `console.log(JSON.stringify(node, null, 2))` in the relevant `optimizeAST()` case
|
|
140
|
+
|
|
141
|
+
### Tag interpolation is not parsed
|
|
142
|
+
|
|
143
|
+
**Symptom:** `#[tag content]` appears as raw text instead of being expanded into markuplint nodes.
|
|
144
|
+
|
|
145
|
+
**Cause:** The text node is not reaching the `HtmlInPugParser` path, or the `#ps:tag-interpolation` detection is failing.
|
|
146
|
+
|
|
147
|
+
**Solution:**
|
|
148
|
+
|
|
149
|
+
1. Check the `Text` case in `nodeize()` — ensure `originNode.raw.includes('#[')` evaluates to `true`
|
|
150
|
+
2. Check the `HtmlInPugParser` — verify `ignoreTags` correctly masks `#[...]`
|
|
151
|
+
3. Check the `#ps:tag-interpolation` detection — ensure `node.nodeName === '#ps:tag-interpolation'`
|
|
152
|
+
4. Check the recursive `PugParser` call — verify offset context (`offsetOffset`, `offsetLine`, `offsetColumn`) is computed correctly
|
|
153
|
+
|
|
154
|
+
### Attribute parsing fails for complex expressions
|
|
155
|
+
|
|
156
|
+
**Symptom:** A Pug attribute with a complex JavaScript value (e.g., `data-value=obj.prop + 1`) causes a parse error or incorrect AST.
|
|
157
|
+
|
|
158
|
+
**Cause:** The `scriptParser()` result is not being handled correctly for multi-token expressions.
|
|
159
|
+
|
|
160
|
+
**Solution:**
|
|
161
|
+
|
|
162
|
+
1. Check the `visitAttr()` method — the final `return` block handles multi-token expressions as `isDynamicValue: true, valueType: 'code'`
|
|
163
|
+
2. Check `scriptParser()` from `@markuplint/parser-utils` — verify it correctly tokenizes the value expression
|
|
164
|
+
3. For single-token expressions, verify the `switch (token.type)` handles the token type correctly
|
|
165
|
+
|
|
166
|
+
### Shorthand attribute has wrong potentialName
|
|
167
|
+
|
|
168
|
+
**Symptom:** `#my-id` or `.my-class` produces an attribute with the wrong `potentialName`.
|
|
169
|
+
|
|
170
|
+
**Cause:** The shorthand detection or `endOffset` recalculation is wrong.
|
|
171
|
+
|
|
172
|
+
**Solution:**
|
|
173
|
+
|
|
174
|
+
1. Check the `#`/`.` branch in `visitAttr()` — ensure `potentialName` is set to `'id'` for `#` and `'class'` for `.`
|
|
175
|
+
2. Check the `endOffset` recalculation in `nodeize()` Tag case — for shorthand attributes, `attr.offset === attr.endOffset` must be true, and `endOffset` should be `attr.offset + attr.val.length - 1`
|
|
176
|
+
3. Verify the `val` value from the Pug AST — for `#my-id`, `val` should be `"'my-id'"` (with surrounding quotes)
|
|
177
|
+
|
|
178
|
+
### Pipeless text not detected
|
|
179
|
+
|
|
180
|
+
**Symptom:** Indented text after a tag with `.` is not treated as pipeless text.
|
|
181
|
+
|
|
182
|
+
**Cause:** The `getPipelessText()` function is not finding `start-pipeless-text` / `end-pipeless-text` tokens, or the line range check is failing.
|
|
183
|
+
|
|
184
|
+
**Solution:**
|
|
185
|
+
|
|
186
|
+
1. Check the lexer output — verify that `start-pipeless-text` and `end-pipeless-text` tokens exist
|
|
187
|
+
2. Check the line range: `startPipelessText.loc.start.line < node.line && node.line < endPipelessText.loc.start.line`
|
|
188
|
+
3. If the tokens exist but the range is wrong, the issue may be in the pug-lexer version
|
package/lib/index.d.ts
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Pug template parser for markuplint. Provides a parser that transforms Pug (formerly Jade)
|
|
4
|
+
* template syntax into markuplint's AST, handling indentation-based nesting, tag interpolation,
|
|
5
|
+
* inline HTML, Pug attributes, mixins, conditionals, and other Pug-specific constructs.
|
|
6
|
+
*/
|
|
1
7
|
export { parser } from './parser.js';
|
package/lib/index.js
CHANGED
|
@@ -1 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @module
|
|
3
|
+
* Pug template parser for markuplint. Provides a parser that transforms Pug (formerly Jade)
|
|
4
|
+
* template syntax into markuplint's AST, handling indentation-based nesting, tag interpolation,
|
|
5
|
+
* inline HTML, Pug attributes, mixins, conditionals, and other Pug-specific constructs.
|
|
6
|
+
*/
|
|
1
7
|
export { parser } from './parser.js';
|
package/lib/parser.d.ts
CHANGED
|
@@ -2,6 +2,14 @@ import type { ASTNode } from './types.js';
|
|
|
2
2
|
import type { MLASTAttr, MLASTNodeTreeItem, MLASTParentNode } from '@markuplint/ml-ast';
|
|
3
3
|
import type { ChildToken, ParseOptions, Token } from '@markuplint/parser-utils';
|
|
4
4
|
import { ParserError, Parser } from '@markuplint/parser-utils';
|
|
5
|
+
/**
|
|
6
|
+
* Parser implementation for Pug templates.
|
|
7
|
+
* Extends the base Parser to handle Pug's indentation-based syntax, including tags,
|
|
8
|
+
* inline HTML, tag interpolation (`#[...]`), Pug attributes (including shorthand
|
|
9
|
+
* `#id` and `.class`), `&attributes` spread syntax, mixins, conditionals,
|
|
10
|
+
* and other Pug-specific constructs. Uses pug-lexer and pug-parser for
|
|
11
|
+
* initial tokenization before mapping to markuplint's node tree.
|
|
12
|
+
*/
|
|
5
13
|
declare class PugParser extends Parser<ASTNode> {
|
|
6
14
|
constructor();
|
|
7
15
|
tokenize(options?: ParseOptions): {
|
|
@@ -9,8 +17,30 @@ declare class PugParser extends Parser<ASTNode> {
|
|
|
9
17
|
isFragment: boolean;
|
|
10
18
|
};
|
|
11
19
|
parseError(error: any): ParserError;
|
|
20
|
+
/**
|
|
21
|
+
* Converts a Pug AST node into markuplint node tree items.
|
|
22
|
+
* Handles Doctype, Text (including inline HTML and tag interpolation),
|
|
23
|
+
* Comment, BlockComment, Tag (with attributes and child blocks),
|
|
24
|
+
* and other Pug-specific constructs (mixins, conditionals, etc.)
|
|
25
|
+
* which are mapped to preprocessor-specific blocks.
|
|
26
|
+
*
|
|
27
|
+
* @param originNode - The Pug AST node to convert
|
|
28
|
+
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
29
|
+
* @param depth - The nesting depth of the node
|
|
30
|
+
* @returns An array of markuplint node tree items
|
|
31
|
+
*/
|
|
12
32
|
nodeize(originNode: ASTNode, parentNode: MLASTParentNode | null, depth: number): readonly MLASTNodeTreeItem[];
|
|
13
33
|
afterFlattenNodes(nodeList: readonly MLASTNodeTreeItem[]): readonly MLASTNodeTreeItem[];
|
|
34
|
+
/**
|
|
35
|
+
* Visits an element token for Pug, constructing a start tag node with
|
|
36
|
+
* pre-parsed attributes (including `&attributes` spread syntax) and
|
|
37
|
+
* visiting child nodes within the Pug block.
|
|
38
|
+
*
|
|
39
|
+
* @param token - The child token with tag metadata and namespace
|
|
40
|
+
* @param childNodes - The child Pug AST nodes within the tag's block
|
|
41
|
+
* @param options - Options containing pre-parsed attribute overrides
|
|
42
|
+
* @returns An array of markuplint node tree items for the element and its children
|
|
43
|
+
*/
|
|
14
44
|
visitElement(token: ChildToken & {
|
|
15
45
|
readonly nodeName: string;
|
|
16
46
|
readonly namespace: string;
|
|
@@ -21,6 +51,14 @@ declare class PugParser extends Parser<ASTNode> {
|
|
|
21
51
|
};
|
|
22
52
|
}): MLASTNodeTreeItem[];
|
|
23
53
|
visitSpreadAttr(): null;
|
|
54
|
+
/**
|
|
55
|
+
* Visits an attribute token, handling Pug-specific syntax including
|
|
56
|
+
* shorthand `#id` and `.class` notation, quoted attribute names,
|
|
57
|
+
* unescaped attribute names (trailing `!`), and JavaScript expression values.
|
|
58
|
+
*
|
|
59
|
+
* @param token - The token representing the attribute
|
|
60
|
+
* @returns The parsed attribute node with Pug-specific metadata
|
|
61
|
+
*/
|
|
24
62
|
visitAttr(token: Token): MLASTAttr;
|
|
25
63
|
}
|
|
26
64
|
export declare const parser: PugParser;
|
package/lib/parser.js
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { HtmlParser, getNamespace } from '@markuplint/html-parser';
|
|
2
2
|
import { ParserError, Parser, AttrState, scriptParser } from '@markuplint/parser-utils';
|
|
3
3
|
import { pugParse } from './pug-parser/index.js';
|
|
4
|
+
/**
|
|
5
|
+
* Internal HTML parser used for inline HTML content within Pug templates.
|
|
6
|
+
* Extends the standard HTML parser to handle Pug tag interpolation syntax (`#[...]`),
|
|
7
|
+
* treating interpolated tags as preprocessor-specific blocks.
|
|
8
|
+
*/
|
|
4
9
|
class HtmlInPugParser extends HtmlParser {
|
|
5
10
|
constructor() {
|
|
6
11
|
super({
|
|
@@ -19,6 +24,14 @@ class HtmlInPugParser extends HtmlParser {
|
|
|
19
24
|
});
|
|
20
25
|
}
|
|
21
26
|
}
|
|
27
|
+
/**
|
|
28
|
+
* Parser implementation for Pug templates.
|
|
29
|
+
* Extends the base Parser to handle Pug's indentation-based syntax, including tags,
|
|
30
|
+
* inline HTML, tag interpolation (`#[...]`), Pug attributes (including shorthand
|
|
31
|
+
* `#id` and `.class`), `&attributes` spread syntax, mixins, conditionals,
|
|
32
|
+
* and other Pug-specific constructs. Uses pug-lexer and pug-parser for
|
|
33
|
+
* initial tokenization before mapping to markuplint's node tree.
|
|
34
|
+
*/
|
|
22
35
|
class PugParser extends Parser {
|
|
23
36
|
constructor() {
|
|
24
37
|
super({
|
|
@@ -43,6 +56,18 @@ class PugParser extends Parser {
|
|
|
43
56
|
}
|
|
44
57
|
return super.parseError(error);
|
|
45
58
|
}
|
|
59
|
+
/**
|
|
60
|
+
* Converts a Pug AST node into markuplint node tree items.
|
|
61
|
+
* Handles Doctype, Text (including inline HTML and tag interpolation),
|
|
62
|
+
* Comment, BlockComment, Tag (with attributes and child blocks),
|
|
63
|
+
* and other Pug-specific constructs (mixins, conditionals, etc.)
|
|
64
|
+
* which are mapped to preprocessor-specific blocks.
|
|
65
|
+
*
|
|
66
|
+
* @param originNode - The Pug AST node to convert
|
|
67
|
+
* @param parentNode - The parent node in the markuplint tree, or null for root nodes
|
|
68
|
+
* @param depth - The nesting depth of the node
|
|
69
|
+
* @returns An array of markuplint node tree items
|
|
70
|
+
*/
|
|
46
71
|
nodeize(
|
|
47
72
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
48
73
|
originNode, parentNode, depth) {
|
|
@@ -189,6 +214,16 @@ class PugParser extends Parser {
|
|
|
189
214
|
exposeWhiteSpace: false,
|
|
190
215
|
});
|
|
191
216
|
}
|
|
217
|
+
/**
|
|
218
|
+
* Visits an element token for Pug, constructing a start tag node with
|
|
219
|
+
* pre-parsed attributes (including `&attributes` spread syntax) and
|
|
220
|
+
* visiting child nodes within the Pug block.
|
|
221
|
+
*
|
|
222
|
+
* @param token - The child token with tag metadata and namespace
|
|
223
|
+
* @param childNodes - The child Pug AST nodes within the tag's block
|
|
224
|
+
* @param options - Options containing pre-parsed attribute overrides
|
|
225
|
+
* @returns An array of markuplint node tree items for the element and its children
|
|
226
|
+
*/
|
|
192
227
|
visitElement(token,
|
|
193
228
|
// eslint-disable-next-line @typescript-eslint/prefer-readonly-parameter-types
|
|
194
229
|
childNodes, options) {
|
|
@@ -210,6 +245,14 @@ class PugParser extends Parser {
|
|
|
210
245
|
visitSpreadAttr() {
|
|
211
246
|
return null;
|
|
212
247
|
}
|
|
248
|
+
/**
|
|
249
|
+
* Visits an attribute token, handling Pug-specific syntax including
|
|
250
|
+
* shorthand `#id` and `.class` notation, quoted attribute names,
|
|
251
|
+
* unescaped attribute names (trailing `!`), and JavaScript expression values.
|
|
252
|
+
*
|
|
253
|
+
* @param token - The token representing the attribute
|
|
254
|
+
* @returns The parsed attribute node with Pug-specific metadata
|
|
255
|
+
*/
|
|
213
256
|
visitAttr(token) {
|
|
214
257
|
if (token.raw[0] === '#' || token.raw[0] === '.') {
|
|
215
258
|
const attr = super.visitAttr(token, {
|
|
@@ -1,2 +1,11 @@
|
|
|
1
1
|
import type { ASTBlock } from '../types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parses a Pug template string into an optimized AST with computed offsets
|
|
4
|
+
* and location data by lexing with pug-lexer, parsing with pug-parser,
|
|
5
|
+
* and then enriching each node with raw source, offsets, and end positions.
|
|
6
|
+
*
|
|
7
|
+
* @param pug - The raw Pug template source code
|
|
8
|
+
* @param useOffset - Whether to strip indent/outdent tokens (used when parsing at a non-zero offset)
|
|
9
|
+
* @returns The optimized Pug AST block containing enriched nodes
|
|
10
|
+
*/
|
|
2
11
|
export declare function pugParse(pug: string, useOffset?: boolean): ASTBlock;
|