@markuplint/php-parser 5.0.0-rc.2 → 5.0.0-rc.5

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 CHANGED
@@ -3,6 +3,18 @@
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.5](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.4...v5.0.0-rc.5) (2026-08-28)
7
+
8
+ **Note:** Version bump only for package @markuplint/php-parser
9
+
10
+ # [5.0.0-rc.4](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.3...v5.0.0-rc.4) (2026-04-19)
11
+
12
+ **Note:** Version bump only for package @markuplint/php-parser
13
+
14
+ # [5.0.0-rc.3](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.2...v5.0.0-rc.3) (2026-04-19)
15
+
16
+ **Note:** Version bump only for package @markuplint/php-parser
17
+
6
18
  # [5.0.0-rc.2](https://github.com/markuplint/markuplint/compare/v5.0.0-rc.1...v5.0.0-rc.2) (2026-04-15)
7
19
 
8
20
  **Note:** Version bump only for package @markuplint/php-parser
package/lib/parser.d.ts CHANGED
@@ -6,6 +6,15 @@ import { HtmlParser } from '@markuplint/html-parser';
6
6
  * - `<?php ... ?>` (standard PHP code blocks; also matches unclosed tags at EOF)
7
7
  * - `<?= ... ?>` (short echo / output tags)
8
8
  * - `<? ... ?>` (short open tags; also matches unclosed tags at EOF)
9
+ *
10
+ * PHP code is never analyzed; only the start/end delimiters are matched, so a
11
+ * `?>` inside a PHP string literal or comment terminates the block early.
12
+ *
13
+ * Known limitation: PHP expressions inside unquoted attribute values are not
14
+ * supported. This limitation is shared by all template engine parsers.
15
+ *
16
+ * @see https://github.com/markuplint/markuplint/issues/240
17
+ * @see https://markuplint.dev/docs/guides/besides-html
9
18
  */
10
19
  declare class PHPParser extends HtmlParser {
11
20
  constructor();
package/lib/parser.js CHANGED
@@ -6,10 +6,22 @@ import { HtmlParser } from '@markuplint/html-parser';
6
6
  * - `<?php ... ?>` (standard PHP code blocks; also matches unclosed tags at EOF)
7
7
  * - `<?= ... ?>` (short echo / output tags)
8
8
  * - `<? ... ?>` (short open tags; also matches unclosed tags at EOF)
9
+ *
10
+ * PHP code is never analyzed; only the start/end delimiters are matched, so a
11
+ * `?>` inside a PHP string literal or comment terminates the block early.
12
+ *
13
+ * Known limitation: PHP expressions inside unquoted attribute values are not
14
+ * supported. This limitation is shared by all template engine parsers.
15
+ *
16
+ * @see https://github.com/markuplint/markuplint/issues/240
17
+ * @see https://markuplint.dev/docs/guides/besides-html
9
18
  */
10
19
  class PHPParser extends HtmlParser {
11
20
  constructor() {
12
21
  super({
22
+ // Ordered from most specific to least specific: the generic `<?` pattern
23
+ // (php-short-tag) must remain last, otherwise it would match before
24
+ // `<?php` and `<?=` and misclassify those tags.
13
25
  ignoreTags: [
14
26
  {
15
27
  type: 'php-tag',
@@ -18,6 +30,8 @@ class PHPParser extends HtmlParser {
18
30
  },
19
31
  {
20
32
  type: 'php-echo',
33
+ // Plain `?>` without the `|$` EOF fallback: echo tags are always
34
+ // expected to be closed within the template.
21
35
  start: '<?=',
22
36
  end: '?>',
23
37
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@markuplint/php-parser",
3
- "version": "5.0.0-rc.2",
3
+ "version": "5.0.0-rc.5",
4
4
  "description": "PHP 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": ">=22"
13
+ "node": ">=24"
14
14
  },
15
15
  "type": "module",
16
16
  "exports": {
@@ -28,10 +28,10 @@
28
28
  "clean": "tsc --build --clean tsconfig.build.json"
29
29
  },
30
30
  "dependencies": {
31
- "@markuplint/html-parser": "5.0.0-rc.2"
31
+ "@markuplint/html-parser": "5.0.0-rc.5"
32
32
  },
33
33
  "devDependencies": {
34
- "@markuplint/parser-utils": "5.0.0-rc.2"
34
+ "@markuplint/parser-utils": "5.0.0-rc.5"
35
35
  },
36
- "gitHead": "e43763858d9234c417053becc73dbd088c1e7ea6"
36
+ "gitHead": "8d87463af2ff3f1b83fb28da20f1819362cf3555"
37
37
  }
@@ -1,87 +0,0 @@
1
- # @markuplint/php-parser
2
-
3
- ## 概要
4
-
5
- `@markuplint/php-parser` は `HtmlParser` を拡張し、PHP コードブロックを含む HTML のリントを可能にします。すべての PHP タグバリアントを不透明ブロックとして扱い、markuplint が PHP 構文に影響されることなく周囲の HTML 構造をリントできるようにします。
6
-
7
- ## 動作の仕組み
8
-
9
- このパーサーは基底の `HtmlParser` が提供する `ignoreTags` メカニズムを使用します:
10
-
11
- 1. **マスク** — パース前に、すべての PHP タグ式(`<?php ... ?>`、`<?= ... ?>`、`<? ... ?>`)が開始/終了デリミタにより識別され、プレースホルダーテキストに置換される
12
- 2. **パース** — マスクされた HTML は、PHP 式が存在しないかのように標準 HTML パーサー(parse5)によってパースされる
13
- 3. **保持** — 元の PHP 式は AST 内に `#ps:*`(PreprocessorSpecificBlock)ノードとして保持され、ソース位置も維持される
14
-
15
- このアプローチにより、markuplint は PHP 構文に影響されることなく HTML 構造をリントできます。
16
-
17
- ## ignoreTags 設定
18
-
19
- `PHPParser` コンストラクタは、正しいマッチングを保証するために、最も具体的なものから順に3つのパターンを定義しています:
20
-
21
- | タイプ | 開始 | 終了 | 説明 |
22
- | --------------- | ------- | ----- | ------------------------- | ----------------------------------------------------- |
23
- | `php-tag` | `<?php` | `/\?> | $/` | 標準 PHP コードブロック(EOF で未閉鎖のタグにも対応) |
24
- | `php-echo` | `<?=` | `?>` | ショートエコー / 出力タグ |
25
- | `php-short-tag` | `<?` | `/\?> | $/` | ショートオープンタグ(EOF で未閉鎖のタグにも対応) |
26
-
27
- **EOF 未閉鎖タグの処理:** `php-tag` と `php-short-tag` パターンは終了デリミタに正規表現 `/\?>|$/` を使用しています。`$` はソースの末尾にマッチし、閉じられていない PHP ブロック(例: ファイル末尾の `<?php include("path/to")`)を単一の `#ps:*` ノードとして正しくキャプチャします。
28
-
29
- `php-echo` パターンはプレーン文字列 `?>` を使用しています。エコータグはテンプレート内で常に閉じられることが想定されているためです。
30
-
31
- ## サポートされない構文
32
-
33
- **引用符なしの属性値**内のテンプレート式はサポートされていません。これはすべてのテンプレートエンジンパーサーに共通する既知の制限です([#240](https://github.com/markuplint/markuplint/issues/240))。[ウェブサイトのドキュメント](https://markuplint.dev/docs/guides/besides-html)も参照してください。
34
-
35
- 使用可能:
36
-
37
- ```html
38
- <div attr="<?php echo value; ?>"></div>
39
- <div attr="<?php echo value; ?>"></div>
40
- <div attr="<?php echo value; ?>-<?php echo value2; ?>-<?php echo value3; ?>"></div>
41
- ```
42
-
43
- 使用不可(引用符なし):
44
-
45
- ```html
46
- <div attr=<?php echo value; ?>></div>
47
- ```
48
-
49
- ## ディレクトリ構成
50
-
51
- ```
52
- src/
53
- ├── index.ts — parser を再エクスポート
54
- ├── parser.ts — HtmlParser を拡張する PHPParser クラス
55
- └── index.spec.ts — パーサー統合テスト
56
- ```
57
-
58
- ## 主要ソースファイル
59
-
60
- | ファイル | 用途 |
61
- | --------------- | ---------------------------------------------------------------- |
62
- | `src/parser.ts` | `PHPParser` クラスを定義し、シングルトン `parser` をエクスポート |
63
- | `src/index.ts` | パッケージエントリーポイント。`parser` を再エクスポート |
64
-
65
- ## 統合ポイント
66
-
67
- ```mermaid
68
- flowchart TD
69
- subgraph upstream ["上流"]
70
- htmlParser["@markuplint/html-parser\n(HtmlParser クラス)"]
71
- end
72
-
73
- subgraph pkg ["@markuplint/php-parser"]
74
- phpParser["PHPParser\nextends HtmlParser\n(ignoreTags のみ)"]
75
- end
76
-
77
- subgraph downstream ["下流"]
78
- mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
79
- end
80
-
81
- htmlParser -->|"継承"| phpParser
82
- phpParser -->|"MLASTDocument を生成"| mlCore
83
- ```
84
-
85
- ## ドキュメントマップ
86
-
87
- - [メンテナンスガイド](docs/maintenance.ja.md) — コマンド、レシピ、テスト
package/ARCHITECTURE.md DELETED
@@ -1,87 +0,0 @@
1
- # @markuplint/php-parser
2
-
3
- ## Overview
4
-
5
- `@markuplint/php-parser` extends `HtmlParser` to lint HTML containing PHP code blocks. It treats all PHP tag variants as opaque blocks, allowing markuplint to lint the surrounding HTML structure without being confused by PHP syntax.
6
-
7
- ## How It Works
8
-
9
- The parser uses the `ignoreTags` mechanism provided by the base `HtmlParser`:
10
-
11
- 1. **Mask** — Before parsing, all PHP tag expressions (`<?php ... ?>`, `<?= ... ?>`, `<? ... ?>`) are identified by their start/end delimiters and replaced with placeholder text
12
- 2. **Parse** — The masked HTML is parsed by the standard HTML parser (parse5) as if the PHP expressions did not exist
13
- 3. **Preserve** — The original PHP expressions are preserved in the AST as `#ps:*` (PreprocessorSpecificBlock) nodes, maintaining their source positions
14
-
15
- This approach allows markuplint to lint the HTML structure without being confused by PHP syntax.
16
-
17
- ## ignoreTags Configuration
18
-
19
- The `PHPParser` constructor defines three ignore patterns, ordered from most specific to least specific to ensure correct matching:
20
-
21
- | Type | Start | End | Description |
22
- | --------------- | ------- | ----- | ------------------------ | ------------------------------------------------------- |
23
- | `php-tag` | `<?php` | `/\?> | $/` | Standard PHP code blocks (also matches unclosed at EOF) |
24
- | `php-echo` | `<?=` | `?>` | Short echo / output tags |
25
- | `php-short-tag` | `<?` | `/\?> | $/` | Short open tags (also matches unclosed at EOF) |
26
-
27
- **EOF-unclosed tag handling:** The `php-tag` and `php-short-tag` patterns use a regex `/\?>|$/` for the end delimiter. The `$` alternative matches the end of the source, allowing PHP blocks that are never closed (e.g., `<?php include("path/to")` at the end of a file) to be correctly captured as a single `#ps:*` node rather than leaving unparsed content.
28
-
29
- The `php-echo` pattern uses a plain string `?>` because echo tags are always expected to be closed within the template.
30
-
31
- ## Unsupported Syntaxes
32
-
33
- Template expressions inside **unquoted attribute values** are not supported. This is a known limitation shared by all template engine parsers ([#240](https://github.com/markuplint/markuplint/issues/240)). See also the [website documentation](https://markuplint.dev/docs/guides/besides-html).
34
-
35
- Available:
36
-
37
- ```html
38
- <div attr="<?php echo value; ?>"></div>
39
- <div attr="<?php echo value; ?>"></div>
40
- <div attr="<?php echo value; ?>-<?php echo value2; ?>-<?php echo value3; ?>"></div>
41
- ```
42
-
43
- Unavailable (unquoted):
44
-
45
- ```html
46
- <div attr=<?php echo value; ?>></div>
47
- ```
48
-
49
- ## Directory Structure
50
-
51
- ```
52
- src/
53
- ├── index.ts — Re-exports parser
54
- ├── parser.ts — PHPParser class extending HtmlParser
55
- └── index.spec.ts — Parser integration tests
56
- ```
57
-
58
- ## Key Source Files
59
-
60
- | File | Purpose |
61
- | --------------- | ----------------------------------------------------------------- |
62
- | `src/parser.ts` | Defines `PHPParser` class and exports singleton `parser` instance |
63
- | `src/index.ts` | Package entry point; re-exports `parser` |
64
-
65
- ## Integration Points
66
-
67
- ```mermaid
68
- flowchart TD
69
- subgraph upstream ["Upstream"]
70
- htmlParser["@markuplint/html-parser\n(HtmlParser class)"]
71
- end
72
-
73
- subgraph pkg ["@markuplint/php-parser"]
74
- phpParser["PHPParser\nextends HtmlParser\n(ignoreTags only)"]
75
- end
76
-
77
- subgraph downstream ["Downstream"]
78
- mlCore["@markuplint/ml-core\n(MLASTDocument → MLDOM)"]
79
- end
80
-
81
- htmlParser -->|"extends"| phpParser
82
- phpParser -->|"produces MLASTDocument"| mlCore
83
- ```
84
-
85
- ## Documentation Map
86
-
87
- - [Maintenance Guide](docs/maintenance.md) — Commands, recipes, and testing
package/SKILL.md DELETED
@@ -1,54 +0,0 @@
1
- ---
2
- description: Perform maintenance tasks for @markuplint/php-parser
3
- globs:
4
- - packages/@markuplint/php-parser/src/**
5
- alwaysApply: false
6
- ---
7
-
8
- # @markuplint/php-parser Maintenance
9
-
10
- You are maintaining `@markuplint/php-parser`, the PHP template parser for markuplint.
11
-
12
- ## Architecture
13
-
14
- See [ARCHITECTURE.md](ARCHITECTURE.md) for the full architecture overview including the ignoreTags mechanism and integration points.
15
-
16
- For detailed maintenance procedures, see [docs/maintenance.md](docs/maintenance.md) ([Japanese](docs/maintenance.ja.md)).
17
-
18
- ## Key Files
19
-
20
- | File | Role |
21
- | --------------- | --------------------------------------------- |
22
- | `src/parser.ts` | PHPParser class with ignoreTags configuration |
23
- | `src/index.ts` | Package entry point; re-exports parser |
24
-
25
- ## Tasks
26
-
27
- ### add-ignore-tag
28
-
29
- Add a new PHP tag variant to the ignoreTags configuration.
30
-
31
- 1. Open `src/parser.ts`
32
- 2. Add a new entry to the `ignoreTags` array in the `PHPParser` constructor
33
- - Place it **before** `php-short-tag` (the most generic pattern must remain last)
34
- - Use a string for the `start` delimiter if it is a fixed prefix
35
- - Use `/\?>|$/` for `end` if the tag may be unclosed at EOF; use `?>` if the tag is always closed
36
- 3. Add a test case in `src/index.spec.ts` under the `Tags` describe block:
37
- ```ts
38
- test('new-type-name', () => {
39
- expect(parse('<new-delimiter any ?>').nodeList[0]?.nodeName).toBe('#ps:new-type-name');
40
- });
41
- ```
42
- 4. Build: `yarn build --scope @markuplint/php-parser`
43
- 5. Test: `yarn test --scope @markuplint/php-parser`
44
-
45
- ### modify-ignore-tag
46
-
47
- Modify an existing PHP tag pattern (start/end delimiter or type name).
48
-
49
- 1. Open `src/parser.ts`
50
- 2. Find the target entry in the `ignoreTags` array and update `type`, `start`, or `end`
51
- 3. Update affected test cases in `src/index.spec.ts`
52
- - Check both `Tags` tests (nodeName assertions) and `Node list` tests (debug map snapshots)
53
- 4. Build: `yarn build --scope @markuplint/php-parser`
54
- 5. Test: `yarn test --scope @markuplint/php-parser`
@@ -1,105 +0,0 @@
1
- # メンテナンスガイド
2
-
3
- ## コマンド
4
-
5
- | コマンド | 説明 |
6
- | ------------------------------------------- | ---------------------- |
7
- | `yarn build --scope @markuplint/php-parser` | このパッケージをビルド |
8
- | `yarn dev --scope @markuplint/php-parser` | ウォッチモードでビルド |
9
- | `yarn clean --scope @markuplint/php-parser` | ビルド成果物を削除 |
10
- | `yarn test --scope @markuplint/php-parser` | テストを実行 |
11
-
12
- ## テスト
13
-
14
- テストファイルは `*.spec.ts` の命名規則に従い、`src/` ディレクトリに配置されています:
15
-
16
- | テストファイル | カバレッジ |
17
- | --------------- | ---------------------------------------------------------------- |
18
- | `index.spec.ts` | PHPParser 統合テスト(エコータグ、ショートタグ、EOF 未閉鎖タグ) |
19
-
20
- 主なテストパターンでは `nodeListToDebugMaps` を使用したスナップショット形式のアサーションを行います:
21
-
22
- ```ts
23
- import { nodeListToDebugMaps } from '@markuplint/parser-utils';
24
- import { parser } from './parser.js';
25
-
26
- const doc = parser.parse('<div><?= name ?></div>');
27
- const debugMaps = nodeListToDebugMaps(doc.nodeList);
28
- expect(debugMaps).toStrictEqual([
29
- // 期待されるデバッグ出力
30
- ]);
31
- ```
32
-
33
- ### タグタイプのアサーション
34
-
35
- 各 PHP タグバリアントには `#ps:*` ノード名を検証する専用テストがあります:
36
-
37
- ```ts
38
- expect(parse('<?php any; ?>').nodeList[0]?.nodeName).toBe('#ps:php-tag');
39
- expect(parse('<?= any ?>').nodeList[0]?.nodeName).toBe('#ps:php-echo');
40
- expect(parse('<? any; ?>').nodeList[0]?.nodeName).toBe('#ps:php-short-tag');
41
- ```
42
-
43
- ### EOF 未閉鎖タグのテスト
44
-
45
- テストスイートでは、閉じ `?>` がない PHP タグが正しくキャプチャされることを検証しています:
46
-
47
- ```ts
48
- expect(parse('<?php any;').nodeList[0]?.nodeName).toBe('#ps:php-tag');
49
- expect(parse('<? any;').nodeList[0]?.nodeName).toBe('#ps:php-short-tag');
50
- ```
51
-
52
- ## レシピ
53
-
54
- ### 1. 新しい PHP タグバリアントの追加
55
-
56
- 1. `src/parser.ts` を開く
57
- 2. `ignoreTags` 配列に新しいエントリを追加:
58
- - `php-short-tag` の**前**に配置する(最も汎用的な `<?` パターンは最後に残す必要あり)
59
- - `start` にはデリミタが固定プレフィックスの場合は文字列を使用
60
- - `end` にはタグが EOF で未閉鎖のまま残る可能性がある場合は `/\?>|$/` を、常に閉じられる場合は `?>` を使用
61
- 3. `src/index.spec.ts` にテストケースを追加:
62
- - `Tags` テストで `#ps:*` ノード名を検証
63
- - `Node list` テストで周囲の HTML を含むデバッグマップ出力を検証
64
- 4. ビルド: `yarn build --scope @markuplint/php-parser`
65
- 5. テスト: `yarn test --scope @markuplint/php-parser`
66
-
67
- ### 2. 既存タグパターンの変更
68
-
69
- 1. `src/parser.ts` を開く
70
- 2. `ignoreTags` 配列内の対象エントリを見つけ、`type`、`start`、または `end` を更新
71
- 3. `src/index.spec.ts` の影響を受けるテストケースを更新:
72
- - `Tags` テスト(nodeName アサーション)と `Node list` テスト(デバッグマップスナップショット)の両方を確認
73
- 4. ビルド: `yarn build --scope @markuplint/php-parser`
74
- 5. テスト: `yarn test --scope @markuplint/php-parser`
75
-
76
- ### 3. 上流 HtmlParser 依存の更新
77
-
78
- 1. `package.json` の `@markuplint/html-parser` 依存を更新
79
- 2. ビルド: `yarn build --scope @markuplint/php-parser`
80
- 3. テスト: `yarn test --scope @markuplint/php-parser`
81
- 4. テストが失敗した場合は `HtmlParser` の変更履歴で `ignoreTags` メカニズムの破壊的変更を確認
82
-
83
- ## トラブルシューティング
84
-
85
- ### PHP タグが認識されない
86
-
87
- **症状:** PHP タグが `#ps:*` ノードではなく、AST 内で生テキストとして表示される。
88
-
89
- **原因:** `start` デリミタが入力とマッチしない、またはより具体的なパターンが先にマッチした。
90
-
91
- **解決策:**
92
-
93
- 1. `ignoreTags` の順序を確認 — より具体的なパターン(例: `<?php`)は、より汎用的なパターン(例: `<?`)の前に配置する必要あり
94
- 2. `start` 文字列が入力内の正確な文字とマッチすることを確認
95
-
96
- ### 未閉鎖の PHP タグがファイルの残り全体を消費する
97
-
98
- **症状:** `?>` で閉じられるべき PHP タグが、代わりにファイルの末尾まで拡張される。
99
-
100
- **原因:** `end` パターンが `/\?>|$/` を使用しており、`?>` が正しくマッチされていない。
101
-
102
- **解決策:**
103
-
104
- 1. `?>` が PHP の文字列リテラルやコメント内にないことを確認(このパーサーは PHP 構文を解析しない — デリミタのマッチングのみ行う)
105
- 2. `end` 正規表現が正しいことを確認: `/\?>|$/`(`\?` はエスケープが必要)
@@ -1,105 +0,0 @@
1
- # Maintenance Guide
2
-
3
- ## Commands
4
-
5
- | Command | Description |
6
- | ------------------------------------------- | ---------------------- |
7
- | `yarn build --scope @markuplint/php-parser` | Build this package |
8
- | `yarn dev --scope @markuplint/php-parser` | Watch mode build |
9
- | `yarn clean --scope @markuplint/php-parser` | Remove build artifacts |
10
- | `yarn test --scope @markuplint/php-parser` | Run tests |
11
-
12
- ## Testing
13
-
14
- Test files follow the `*.spec.ts` naming convention and are located in the `src/` directory:
15
-
16
- | Test File | Coverage |
17
- | --------------- | ------------------------------------------------------------------------- |
18
- | `index.spec.ts` | PHPParser integration tests (echo tags, short tags, unclosed tags at EOF) |
19
-
20
- The primary testing pattern uses `nodeListToDebugMaps` for snapshot-style assertions:
21
-
22
- ```ts
23
- import { nodeListToDebugMaps } from '@markuplint/parser-utils';
24
- import { parser } from './parser.js';
25
-
26
- const doc = parser.parse('<div><?= name ?></div>');
27
- const debugMaps = nodeListToDebugMaps(doc.nodeList);
28
- expect(debugMaps).toStrictEqual([
29
- // expected debug output
30
- ]);
31
- ```
32
-
33
- ### Tag type assertions
34
-
35
- Each PHP tag variant has a dedicated test verifying its `#ps:*` node name:
36
-
37
- ```ts
38
- expect(parse('<?php any; ?>').nodeList[0]?.nodeName).toBe('#ps:php-tag');
39
- expect(parse('<?= any ?>').nodeList[0]?.nodeName).toBe('#ps:php-echo');
40
- expect(parse('<? any; ?>').nodeList[0]?.nodeName).toBe('#ps:php-short-tag');
41
- ```
42
-
43
- ### EOF-unclosed tag tests
44
-
45
- The test suite verifies that PHP tags without a closing `?>` are correctly captured:
46
-
47
- ```ts
48
- expect(parse('<?php any;').nodeList[0]?.nodeName).toBe('#ps:php-tag');
49
- expect(parse('<? any;').nodeList[0]?.nodeName).toBe('#ps:php-short-tag');
50
- ```
51
-
52
- ## Recipes
53
-
54
- ### 1. Adding a New PHP Tag Variant
55
-
56
- 1. Open `src/parser.ts`
57
- 2. Add a new entry to the `ignoreTags` array:
58
- - Place it **before** `php-short-tag` (the most generic `<?` pattern must remain last)
59
- - Use a string for `start` if the delimiter is a fixed prefix
60
- - Use `/\?>|$/` for `end` if the tag may remain unclosed at EOF; use `?>` if the tag is always closed
61
- 3. Add test cases in `src/index.spec.ts`:
62
- - A `Tags` test verifying the `#ps:*` node name
63
- - A `Node list` test verifying the debug map output with surrounding HTML
64
- 4. Build: `yarn build --scope @markuplint/php-parser`
65
- 5. Test: `yarn test --scope @markuplint/php-parser`
66
-
67
- ### 2. Modifying an Existing Tag Pattern
68
-
69
- 1. Open `src/parser.ts`
70
- 2. Find the target entry in the `ignoreTags` array and update `type`, `start`, or `end`
71
- 3. Update affected test cases in `src/index.spec.ts`:
72
- - Check both `Tags` tests (nodeName assertions) and `Node list` tests (debug map snapshots)
73
- 4. Build: `yarn build --scope @markuplint/php-parser`
74
- 5. Test: `yarn test --scope @markuplint/php-parser`
75
-
76
- ### 3. Updating the Upstream HtmlParser Dependency
77
-
78
- 1. Update the `@markuplint/html-parser` dependency in `package.json`
79
- 2. Build: `yarn build --scope @markuplint/php-parser`
80
- 3. Test: `yarn test --scope @markuplint/php-parser`
81
- 4. If tests fail, check the `HtmlParser` changelog for breaking changes in the `ignoreTags` mechanism
82
-
83
- ## Troubleshooting
84
-
85
- ### PHP tag is not recognized
86
-
87
- **Symptom:** A PHP tag appears as raw text in the AST instead of a `#ps:*` node.
88
-
89
- **Cause:** The `start` delimiter does not match the input, or a more specific pattern matched first.
90
-
91
- **Solution:**
92
-
93
- 1. Check the ordering of `ignoreTags` — more specific patterns (e.g., `<?php`) must appear before less specific ones (e.g., `<?`)
94
- 2. Verify the `start` string matches the exact characters in the input
95
-
96
- ### Unclosed PHP tag consumes the rest of the file
97
-
98
- **Symptom:** A PHP tag that should be closed by `?>` instead extends to the end of the file.
99
-
100
- **Cause:** The `end` pattern uses `/\?>|$/` and the `?>` is not being matched correctly.
101
-
102
- **Solution:**
103
-
104
- 1. Verify the `?>` is not inside a PHP string literal or comment (this parser does not analyze PHP syntax — it only matches delimiters)
105
- 2. Check that the `end` regex is correct: `/\?>|$/` (the `\?` must be escaped)