@markuplint/parser-utils 4.8.11 → 5.0.0-alpha.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.
@@ -57,7 +57,7 @@ flowchart TD
57
57
  mlAst["@markuplint/ml-ast\n型定義"]
58
58
  mlSpec["@markuplint/ml-spec\nvoid 要素"]
59
59
  espree["espree\nJS トークナイザ"]
60
- uuidLib["uuid\nノード ID"]
60
+ cryptoLib["node:crypto\nノード ID"]
61
61
  end
62
62
 
63
63
  parser --> attrTokenizer
@@ -79,7 +79,7 @@ flowchart TD
79
79
 
80
80
  parser --> mlAst
81
81
  parser --> mlSpec
82
- parser --> uuidLib
82
+ parser --> cryptoLib
83
83
  decision --> mlAst
84
84
  ```
85
85
 
@@ -95,12 +95,12 @@ flowchart TD
95
95
  | `ignore-block.ts` | テンプレート式のマスキングと復元 | `ignoreBlock`, `restoreNode` |
96
96
  | `ignore-front-matter.ts` | YAML フロントマター処理 | `ignoreFrontMatter` |
97
97
  | `detect-element-type.ts` | 要素の分類 | `detectElementType` |
98
- | `idl-attributes.ts` | IDL ↔ コンテンツ属性名マッピング | `searchIDLAttribute` |
98
+ | `idl-attributes.ts` | IDL ↔ コンテンツ属性名マッピング | `searchIDLAttribute` |
99
99
  | `debugger.ts` | テスト・デバッグユーティリティ | `nodeListToDebugMaps`, `attributesToDebugMaps`, `nodeTreeDebugView` |
100
100
  | `parser-error.ts` | エラークラス | `ParserError`, `TargetParserError`, `ConfigParserError` |
101
101
  | `sort-nodes.ts` | ノードの位置ソート | `sortNodes` |
102
102
  | `const.ts` | 定数 | `MASK_CHAR`, `svgElementList`, `defaultSpaces` |
103
- | `get-location.ts` | 位置計算 | `getPosition`, `getEndLine`, `getEndCol`, `getOffsetsFromCode` |
103
+ | `get-location.ts` | 位置計算 | `getPosition`, `getEndLine`, `getEndCol`, `getEndPosition`, `getOffsetsFromCode` |
104
104
  | `decision.ts` | カスタム要素名判定 | `isPotentialCustomElementName`, `isSVGElement` |
105
105
 
106
106
  ## パースパイプライン概要
@@ -148,7 +148,7 @@ ParserError (基底クラス)
148
148
 
149
149
  ## IDL 属性マッピング
150
150
 
151
- `searchIDLAttribute` は React スタイルの IDL 属性名と HTML コンテンツ属性名の双方向マッピングを提供します(例: `className` → `class`、`htmlFor` → `for`)。JSX パーサーが属性の `potentialName` を解決する際に使用されます。
151
+ `searchIDLAttribute` は React スタイルの IDL 属性名と HTML コンテンツ属性名の双方向マッピングを提供します(例: `className` → `class`、`htmlFor` → `for`)。spec `useIDLAttributeNames: true` を設定している場合に、`@markuplint/ml-core` の `MLAttr` コンストラクタから呼び出されます。
152
152
 
153
153
  ## 外部依存
154
154
 
@@ -157,7 +157,7 @@ ParserError (基底クラス)
157
157
  | `@markuplint/ml-ast` | AST 型定義 |
158
158
  | `@markuplint/ml-spec` | void 要素判定 |
159
159
  | `@markuplint/types` | カスタム要素名検証 |
160
- | `uuid` | AST ノード UUID 生成 |
160
+ | `node:crypto` | AST ノード UUID 生成 |
161
161
  | `debug` | パフォーマンスタイミング・ロギング |
162
162
  | `espree` | JavaScript トークン化・パース |
163
163
  | `type-fest` | TypeScript ユーティリティ型 |
package/ARCHITECTURE.md CHANGED
@@ -67,7 +67,7 @@ flowchart TD
67
67
  mlSpec["@markuplint/ml-spec\n(void element detection)"]
68
68
  mlTypes["@markuplint/types\n(custom element validation)"]
69
69
  espree["espree\n(JS tokenization)"]
70
- uuidLib["uuid\n(node ID generation)"]
70
+ cryptoLib["node:crypto\n(node ID generation)"]
71
71
  debugLib["debug\n(logging)"]
72
72
  end
73
73
 
@@ -90,7 +90,7 @@ flowchart TD
90
90
  decision --> mlTypes
91
91
  parser --> mlAst
92
92
  parser --> mlSpec
93
- parser --> uuidLib
93
+ parser --> cryptoLib
94
94
  scriptParser --> espree
95
95
  debugMod --> debugLib
96
96
  ```
@@ -113,7 +113,7 @@ flowchart TD
113
113
  | `parser-error.ts` | Error classes with positional information | `ParserError`, `TargetParserError`, `ConfigParserError` |
114
114
  | `sort-nodes.ts` | Node position sorting by offset | `sortNodes` |
115
115
  | `const.ts` | Constants used across the package | `MASK_CHAR`, `svgElementList`, `defaultSpaces` |
116
- | `get-location.ts` | Line/column/offset position calculations | `getPosition`, `getEndLine`, `getEndCol`, `getOffsetsFromCode` |
116
+ | `get-location.ts` | Line/column/offset position calculations | `getPosition`, `getEndLine`, `getEndCol`, `getEndPosition`, `getOffsetsFromCode` |
117
117
  | `decision.ts` | Custom element name detection and SVG element lookup | `isPotentialCustomElementName`, `isSVGElement` |
118
118
 
119
119
  ## Parse Pipeline Overview
@@ -174,7 +174,7 @@ Additionally, `debug.ts` provides `PerformanceTimer` for measuring parse phase d
174
174
 
175
175
  ## IDL Attribute Mapping
176
176
 
177
- `searchIDLAttribute` maps between React-style IDL attribute names and HTML content attribute names. It maintains a comprehensive mapping table derived from React's `possibleStandardNames.js`, covering:
177
+ `searchIDLAttribute` maps between React-style IDL attribute names and HTML content attribute names. It is called by `@markuplint/ml-core`'s `MLAttr` constructor when the spec sets `useIDLAttributeNames: true`. It maintains a comprehensive mapping table derived from React's `possibleStandardNames.js`, covering:
178
178
 
179
179
  - HTML attributes (e.g., `className` -> `class`, `htmlFor` -> `for`, `tabIndex` -> `tabindex`)
180
180
  - SVG attributes (e.g., `strokeWidth` -> `stroke-width`, `clipPath` -> `clip-path`)
@@ -189,7 +189,7 @@ The lookup handles camelCase IDL names, lowercase content attribute names, and h
189
189
  | `@markuplint/ml-ast` | AST type definitions (`MLASTDocument`, `MLASTElement`, `MLASTToken`, etc.) |
190
190
  | `@markuplint/ml-spec` | Void element detection (`isVoidElement`) for self-closing tag handling |
191
191
  | `@markuplint/types` | Custom element name validation (`isCustomElementName`) |
192
- | `uuid` | AST node UUID generation for unique node identification |
192
+ | `node:crypto` | AST node UUID generation via `crypto.randomUUID()` |
193
193
  | `debug` | Performance timing and structured logging |
194
194
  | `espree` | JavaScript tokenization and parsing for embedded script content |
195
195
  | `type-fest` | TypeScript utility types |
package/CHANGELOG.md CHANGED
@@ -3,6 +3,37 @@
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.0](https://github.com/markuplint/markuplint/compare/v4.14.1...v5.0.0-alpha.0) (2026-02-20)
7
+
8
+ ### Bug Fixes
9
+
10
+ - **ml-core:** improve detection of namespace ([5b507ad](https://github.com/markuplint/markuplint/commit/5b507ad7c19c5015b8ce587845d901e31dfa6518))
11
+ - resolve additional eslint-plugin-unicorn v63 errors ([e58a72c](https://github.com/markuplint/markuplint/commit/e58a72c17c97bbec522f9513b99777fac6904d64))
12
+ - use explicit `export type` for type-only re-exports ([7c77c05](https://github.com/markuplint/markuplint/commit/7c77c05619518c8d18a183132040f5b2cd0ab6ec))
13
+
14
+ - feat(parser-utils)!: adapt to simplified MLASTToken properties ([5cbbc9c](https://github.com/markuplint/markuplint/commit/5cbbc9ca8f77a71d99bffa14b193c79b26c1c415))
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ - Update Token type and parser internals for
19
+ simplified AST token properties.
20
+
21
+ Token type property renames:
22
+
23
+ - startOffset -> offset
24
+ - startLine -> line
25
+ - startCol -> col
26
+
27
+ Parser changes:
28
+
29
+ - createToken() no longer produces endOffset/endLine/endCol
30
+ - visitPsBlock() parameter: conditionalType -> blockBehavior
31
+ - visitElement() accepts blockBehavior option
32
+ - Remove selfClosingSolidus token generation
33
+ - Add getEndPosition() helper to get-location.ts
34
+
35
+ Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
36
+
6
37
  ## [4.8.11](https://github.com/markuplint/markuplint/compare/@markuplint/parser-utils@4.8.10...@markuplint/parser-utils@4.8.11) (2026-02-10)
7
38
 
8
39
  **Note:** Version bump only for package @markuplint/parser-utils
@@ -255,12 +255,12 @@ doctype 名、パブリック ID、システム ID を含むトークンから d
255
255
  visitPsBlock(
256
256
  token: ChildToken & { nodeName: string; isFragment: boolean },
257
257
  childNodes?: readonly Node[],
258
- conditionalType?: MLASTPreprocessorSpecificBlockConditionalType,
258
+ blockBehavior?: MLASTBlockBehavior | null,
259
259
  originBlockNode?: Node
260
260
  ): readonly MLASTNodeTreeItem[]
261
261
  ```
262
262
 
263
- プリプロセッサ固有のブロックノードを作成します。`nodeName` には自動的に `#ps:` プレフィックスが付加されます(例: `#ps:if`、`#ps:each`、`#ps:front-matter`)。`visitChildren()` を通じて子ノードを再帰的にトラバースします。
263
+ プリプロセッサ固有のブロックノードを作成します。`nodeName` には自動的に `#ps:` プレフィックスが付加されます(例: `#ps:if`、`#ps:each`、`#ps:front-matter`)。`blockBehavior` パラメータはブロックの制御フロー(型と式)を記述します。`visitChildren()` を通じて子ノードを再帰的にトラバースします。
264
264
 
265
265
  ### visitAttr()
266
266
 
@@ -347,10 +347,10 @@ stateDiagram-v2
347
347
 
348
348
  ```ts
349
349
  createToken(token: Token): MLASTToken;
350
- createToken(token: string, startOffset: number, startLine: number, startCol: number): MLASTToken;
350
+ createToken(token: string, offset: number, line: number, col: number): MLASTToken;
351
351
  ```
352
352
 
353
- 生成された UUID(8 文字)と計算された終了位置を持つ新しい `MLASTToken` を作成します。`Token` オブジェクトまたは明示的な座標を持つ生の文字列を受け取ります。
353
+ 生成された UUID(8 文字)を持つ新しい `MLASTToken` を作成します。`Token` オブジェクトまたは明示的な座標を持つ生の文字列を受け取ります。
354
354
 
355
355
  ### sliceFragment()
356
356
 
@@ -412,11 +412,11 @@ walk<Node extends MLASTNodeTreeItem>(
412
412
  ```ts
413
413
  updateLocation(
414
414
  node: MLASTNodeTreeItem,
415
- props: Partial<Pick<MLASTNodeTreeItem, 'startOffset' | 'startLine' | 'startCol' | 'depth'>>
415
+ props: Partial<Pick<MLASTNodeTreeItem, 'offset' | 'line' | 'col' | 'depth'>>
416
416
  ): void
417
417
  ```
418
418
 
419
- AST ノードの位置と深さのプロパティを更新し、新しい開始値から終了オフセット/行/列を再計算します。
419
+ AST ノードの位置と深さのプロパティを更新します。
420
420
 
421
421
  ### updateRaw()
422
422
 
@@ -255,12 +255,12 @@ Creates a doctype node from a token containing the doctype name, public ID, and
255
255
  visitPsBlock(
256
256
  token: ChildToken & { nodeName: string; isFragment: boolean },
257
257
  childNodes?: readonly Node[],
258
- conditionalType?: MLASTPreprocessorSpecificBlockConditionalType,
258
+ blockBehavior?: MLASTBlockBehavior | null,
259
259
  originBlockNode?: Node
260
260
  ): readonly MLASTNodeTreeItem[]
261
261
  ```
262
262
 
263
- Creates a preprocessor-specific block node. The `nodeName` is automatically prefixed with `#ps:` (e.g., `#ps:if`, `#ps:each`, `#ps:front-matter`). Recursively traverses child nodes via `visitChildren()`.
263
+ Creates a preprocessor-specific block node. The `nodeName` is automatically prefixed with `#ps:` (e.g., `#ps:if`, `#ps:each`, `#ps:front-matter`). The `blockBehavior` parameter describes the control-flow semantics (type and expression) of the block. Recursively traverses child nodes via `visitChildren()`.
264
264
 
265
265
  ### visitAttr()
266
266
 
@@ -347,10 +347,10 @@ stateDiagram-v2
347
347
 
348
348
  ```ts
349
349
  createToken(token: Token): MLASTToken;
350
- createToken(token: string, startOffset: number, startLine: number, startCol: number): MLASTToken;
350
+ createToken(token: string, offset: number, line: number, col: number): MLASTToken;
351
351
  ```
352
352
 
353
- Creates a new `MLASTToken` with a generated UUID (8 chars) and computed end position. Accepts either a `Token` object or a raw string with explicit coordinates.
353
+ Creates a new `MLASTToken` with a generated UUID (8 chars). Accepts either a `Token` object or a raw string with explicit coordinates.
354
354
 
355
355
  ### sliceFragment()
356
356
 
@@ -412,11 +412,11 @@ Walks a node list depth-first, invoking the walker callback for each node. The w
412
412
  ```ts
413
413
  updateLocation(
414
414
  node: MLASTNodeTreeItem,
415
- props: Partial<Pick<MLASTNodeTreeItem, 'startOffset' | 'startLine' | 'startCol' | 'depth'>>
415
+ props: Partial<Pick<MLASTNodeTreeItem, 'offset' | 'line' | 'col' | 'depth'>>
416
416
  ): void
417
417
  ```
418
418
 
419
- Updates position and depth properties of an AST node, recalculating end offsets/lines/columns from the new start values.
419
+ Updates position and depth properties of an AST node.
420
420
 
421
421
  ### updateRaw()
422
422
 
package/lib/debug.js CHANGED
@@ -1,15 +1,3 @@
1
- var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
2
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
3
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
4
- return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
5
- };
6
- var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
7
- if (kind === "m") throw new TypeError("Private method is not writable");
8
- if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
9
- if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
10
- return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
11
- };
12
- var _PerformanceTimer_logs, _PerformanceTimer_counter;
13
1
  import debug from 'debug';
14
2
  import { nodeListToDebugMaps } from './debugger.js';
15
3
  export const log = debug('ml-parser');
@@ -17,32 +5,29 @@ export function domLog(nodeList) {
17
5
  log('Parse result: %O', nodeListToDebugMaps(nodeList, true));
18
6
  }
19
7
  export class PerformanceTimer {
20
- constructor() {
21
- _PerformanceTimer_logs.set(this, []);
22
- _PerformanceTimer_counter.set(this, -1);
23
- }
8
+ #logs = [];
9
+ #counter = -1;
24
10
  push(name) {
25
- var _a;
26
11
  if (!log.enabled) {
27
12
  return '';
28
13
  }
29
- __classPrivateFieldSet(this, _PerformanceTimer_counter, (_a = __classPrivateFieldGet(this, _PerformanceTimer_counter, "f"), _a++, _a), "f");
14
+ this.#counter++;
30
15
  const now = performance.now();
31
- const last = __classPrivateFieldGet(this, _PerformanceTimer_logs, "f").at(-1);
16
+ const last = this.#logs.at(-1);
32
17
  if (last && Number.isNaN(last[2])) {
33
18
  last[2] = now;
34
19
  }
35
- name = name || `#${__classPrivateFieldGet(this, _PerformanceTimer_counter, "f")}`;
36
- __classPrivateFieldGet(this, _PerformanceTimer_logs, "f").push([name, now, Number.NaN]);
20
+ name = name || `#${this.#counter}`;
21
+ this.#logs.push([name, now, Number.NaN]);
37
22
  }
38
23
  log() {
39
24
  if (!log.enabled) {
40
25
  return;
41
26
  }
42
27
  this.push('end');
43
- __classPrivateFieldGet(this, _PerformanceTimer_logs, "f").pop();
28
+ this.#logs.pop();
44
29
  const map = new Map();
45
- for (const content of __classPrivateFieldGet(this, _PerformanceTimer_logs, "f")) {
30
+ for (const content of this.#logs) {
46
31
  const diff = content[2] - content[1];
47
32
  const name = content[0];
48
33
  if (map.has(name)) {
@@ -59,4 +44,3 @@ export class PerformanceTimer {
59
44
  }
60
45
  }
61
46
  }
62
- _PerformanceTimer_logs = new WeakMap(), _PerformanceTimer_counter = new WeakMap();
package/lib/debugger.js CHANGED
@@ -1,3 +1,4 @@
1
+ import { getEndCol, getEndLine } from './get-location.js';
1
2
  /**
2
3
  * Converts a list of AST nodes into human-readable debug strings showing
3
4
  * each node's position, type, and raw content. Useful for snapshot testing.
@@ -8,8 +9,7 @@
8
9
  */
9
10
  export function nodeListToDebugMaps(nodeList, withAttr = false) {
10
11
  return nodeList.flatMap(n => {
11
- const r = [];
12
- r.push(tokenDebug(n));
12
+ const r = [tokenDebug(n)];
13
13
  if (withAttr && n && n.type === 'starttag') {
14
14
  r.push(...attributesToDebugMaps(n.attributes).flat());
15
15
  }
@@ -86,9 +86,14 @@ function tokenDebug(n, type = '') {
86
86
  if (!n) {
87
87
  return 'NULL';
88
88
  }
89
- return `[${n.startLine}:${n.startCol}]>[${n.endLine}:${n.endCol}](${n.startOffset},${n.endOffset})${
89
+ const endOffset = n.offset + n.raw.length;
90
+ const endLine = getEndLine(n.raw, n.line);
91
+ const endCol = getEndCol(n.raw, n.col);
92
+ return `[${n.line}:${n.col}]>[${endLine}:${endCol}](${n.offset},${endOffset})${
90
93
  // @ts-ignore
91
- n.potentialName ?? n.nodeName ?? n.name ?? n.type ?? type}${'isGhost' in n && n.isGhost ? '(👻)' : ''}${'isBogus' in n && n.isBogus ? '(👿)' : ''}${'conditionalType' in n && n.conditionalType ? ` (${n.conditionalType})` : ''}: ${visibleWhiteSpace(n.raw)}`;
94
+ n.potentialName ?? n.nodeName ?? n.name ?? n.type ?? type}${'isGhost' in n && n.isGhost ? '(👻)' : ''}${'isBogus' in n && n.isBogus ? '(👿)' : ''}${
95
+ // @ts-ignore
96
+ n.blockBehavior?.type ? ` (${n.blockBehavior.type})` : ''}: ${visibleWhiteSpace(n.raw)}`;
92
97
  }
93
98
  function visibleWhiteSpace(chars) {
94
99
  return chars.replaceAll('\n', '⏎').replaceAll('\t', '→').replaceAll(/\s/g, '␣');
@@ -6,12 +6,43 @@ export declare function getLine(rawCodeFragment: string, startOffset: number): n
6
6
  * @deprecated Use {@link getPosition} instead. Will be removed in v5.0.0.
7
7
  */
8
8
  export declare function getCol(rawCodeFragment: string, startOffset: number): number;
9
+ /**
10
+ * Computes the line and column of a position within a code fragment.
11
+ *
12
+ * @param rawCodeFragment - The full raw source text
13
+ * @param startOffset - The zero-based byte offset to compute the position of
14
+ * @returns An object containing one-based `line` and `column`
15
+ */
9
16
  export declare function getPosition(rawCodeFragment: string, startOffset: number): {
10
17
  readonly line: number;
11
18
  readonly column: number;
12
19
  };
13
20
  export declare function getEndLine(rawCodeFragment: string, startLine: number): number;
14
21
  export declare function getEndCol(rawCodeFragment: string, startCol: number): number;
22
+ /**
23
+ * Computes the end position of a code fragment given its start position.
24
+ *
25
+ * @param rawCodeFragment - The raw source text of the fragment
26
+ * @param startOffset - The zero-based byte offset where the fragment starts
27
+ * @param startLine - The one-based line number where the fragment starts
28
+ * @param startCol - The one-based column number where the fragment starts
29
+ * @returns An object containing `endOffset`, `endLine`, and `endCol`
30
+ */
31
+ export declare function getEndPosition(rawCodeFragment: string, startOffset: number, startLine: number, startCol: number): {
32
+ endOffset: number;
33
+ endLine: number;
34
+ endCol: number;
35
+ };
36
+ /**
37
+ * Converts line/column ranges to byte offsets within a code string.
38
+ *
39
+ * @param rawCode - The full raw source text
40
+ * @param startLine - The one-based start line number
41
+ * @param startCol - The one-based start column number
42
+ * @param endLine - The one-based end line number
43
+ * @param endCol - The one-based end column number
44
+ * @returns An object containing zero-based `offset` and `endOffset`
45
+ */
15
46
  export declare function getOffsetsFromCode(rawCode: string, startLine: number, startCol: number, endLine: number, endCol: number): {
16
47
  offset: number;
17
48
  endOffset: number;
@@ -12,6 +12,13 @@ export function getCol(rawCodeFragment, startOffset) {
12
12
  const lines = rawCodeFragment.slice(0, startOffset).split(LINE_BREAK);
13
13
  return (lines.at(-1) ?? '').length + 1;
14
14
  }
15
+ /**
16
+ * Computes the line and column of a position within a code fragment.
17
+ *
18
+ * @param rawCodeFragment - The full raw source text
19
+ * @param startOffset - The zero-based byte offset to compute the position of
20
+ * @returns An object containing one-based `line` and `column`
21
+ */
15
22
  export function getPosition(rawCodeFragment, startOffset) {
16
23
  const lines = rawCodeFragment.slice(0, startOffset).split(LINE_BREAK);
17
24
  const line = lines.length;
@@ -27,6 +34,32 @@ export function getEndCol(rawCodeFragment, startCol) {
27
34
  const lastLine = lines.pop();
28
35
  return lineCount > 1 ? lastLine.length + 1 : startCol + rawCodeFragment.length;
29
36
  }
37
+ /**
38
+ * Computes the end position of a code fragment given its start position.
39
+ *
40
+ * @param rawCodeFragment - The raw source text of the fragment
41
+ * @param startOffset - The zero-based byte offset where the fragment starts
42
+ * @param startLine - The one-based line number where the fragment starts
43
+ * @param startCol - The one-based column number where the fragment starts
44
+ * @returns An object containing `endOffset`, `endLine`, and `endCol`
45
+ */
46
+ export function getEndPosition(rawCodeFragment, startOffset, startLine, startCol) {
47
+ return {
48
+ endOffset: startOffset + rawCodeFragment.length,
49
+ endLine: getEndLine(rawCodeFragment, startLine),
50
+ endCol: getEndCol(rawCodeFragment, startCol),
51
+ };
52
+ }
53
+ /**
54
+ * Converts line/column ranges to byte offsets within a code string.
55
+ *
56
+ * @param rawCode - The full raw source text
57
+ * @param startLine - The one-based start line number
58
+ * @param startCol - The one-based start column number
59
+ * @param endLine - The one-based end line number
60
+ * @param endCol - The one-based end column number
61
+ * @returns An object containing zero-based `offset` and `endOffset`
62
+ */
30
63
  export function getOffsetsFromCode(rawCode, startLine, startCol, endLine, endCol) {
31
64
  const lines = rawCode.split('\n');
32
65
  let offset = 0;
@@ -1,2 +1,11 @@
1
1
  import type { MLASTParentNode, NamespaceURI } from '@markuplint/ml-ast';
2
+ /**
3
+ * Determines the namespace URI for a node based on its tag name and parent node context.
4
+ * Handles namespace transitions between HTML, SVG, and MathML
5
+ * (e.g., entering `<svg>` switches to SVG namespace, `<foreignObject>` switches back to HTML).
6
+ *
7
+ * @param currentNodeName - The tag name of the current node, or null for anonymous nodes
8
+ * @param parentNode - The parent node in the AST, or null for root-level nodes
9
+ * @returns The resolved namespace URI for the node
10
+ */
2
11
  export declare function getNamespace(currentNodeName: string | null, parentNode: MLASTParentNode | null): NamespaceURI;
@@ -1,3 +1,12 @@
1
+ /**
2
+ * Determines the namespace URI for a node based on its tag name and parent node context.
3
+ * Handles namespace transitions between HTML, SVG, and MathML
4
+ * (e.g., entering `<svg>` switches to SVG namespace, `<foreignObject>` switches back to HTML).
5
+ *
6
+ * @param currentNodeName - The tag name of the current node, or null for anonymous nodes
7
+ * @param parentNode - The parent node in the AST, or null for root-level nodes
8
+ * @returns The resolved namespace URI for the node
9
+ */
1
10
  export function getNamespace(currentNodeName, parentNode) {
2
11
  const parentNS = getParentNamespace(parentNode);
3
12
  if (parentNS === 'http://www.w3.org/1999/xhtml' && currentNodeName?.toLowerCase() === 'svg') {
@@ -15,11 +15,10 @@ export function ignoreBlock(source, tags, maskChar = MASK_CHAR) {
15
15
  replaced = text.replaced;
16
16
  stack.push(...text.stack.map(res => ({ ...res, type: tag.type })));
17
17
  }
18
- stack.sort((a, b) => a.index - b.index);
19
18
  return {
20
19
  source,
21
20
  replaced,
22
- stack,
21
+ stack: stack.toSorted((a, b) => a.index - b.index),
23
22
  maskChar,
24
23
  };
25
24
  }
@@ -63,33 +62,34 @@ ignoreBlock, throwErrorWhenTagHasUnresolved = true) {
63
62
  for (const tag of stack) {
64
63
  const raw = `${tag.startTag}${tag.taggedCode}${tag.endTag ?? ''}`;
65
64
  const tagIndexEnd = tag.index + raw.length;
66
- const node = newNodeList.find(node => node.startOffset <= tag.index && node.endOffset >= tagIndexEnd);
65
+ const nodeEndOffset = (n) => n.offset + n.raw.length;
66
+ const node = newNodeList.find(node => node.offset <= tag.index && nodeEndOffset(node) >= tagIndexEnd);
67
67
  if (!node) {
68
68
  continue;
69
69
  }
70
70
  const replacementChildNodes = [];
71
- if (node.startOffset === tag.index && node.endOffset === tagIndexEnd) {
72
- const token = parser.createToken(raw, node.startOffset, node.startLine, node.startCol);
71
+ if (node.offset === tag.index && nodeEndOffset(node) === tagIndexEnd) {
72
+ const token = parser.createToken(raw, node.offset, node.line, node.col);
73
73
  const psNode = {
74
74
  ...token,
75
75
  type: 'psblock',
76
- conditionalType: null,
77
76
  depth: node.depth,
78
77
  nodeName: `#ps:${tag.type}`,
79
78
  parentNode: node.parentNode,
80
79
  childNodes: [],
80
+ blockBehavior: null,
81
81
  isBogus: false,
82
82
  isFragment: false, // TODO: Case by case
83
83
  };
84
84
  replacementChildNodes.push(psNode);
85
85
  }
86
86
  else if (node.type === 'text') {
87
- const offset = tag.index - node.startOffset;
87
+ const offset = tag.index - node.offset;
88
88
  const above = node.raw.slice(0, offset);
89
89
  const below = node.raw.slice(offset + raw.length);
90
90
  if (above) {
91
91
  const { line, column } = getPosition(node.raw, 0);
92
- const token = parser.createToken(above, node.startOffset, node.startLine + line - 1, node.startCol + column - 1);
92
+ const token = parser.createToken(above, node.offset, node.line + line - 1, node.col + column - 1);
93
93
  const aboveNode = {
94
94
  ...token,
95
95
  nodeName: '#text',
@@ -100,22 +100,22 @@ ignoreBlock, throwErrorWhenTagHasUnresolved = true) {
100
100
  replacementChildNodes.push(aboveNode);
101
101
  }
102
102
  const { line, column } = getPosition(raw, offset);
103
- const token = parser.createToken(raw, node.startOffset + offset, node.startLine + line - 1, node.startCol + column - 1);
103
+ const token = parser.createToken(raw, node.offset + offset, node.line + line - 1, node.col + column - 1);
104
104
  const psNode = {
105
105
  ...token,
106
106
  type: 'psblock',
107
- conditionalType: null,
108
107
  depth: node.depth,
109
108
  nodeName: `#ps:${tag.type}`,
110
109
  parentNode: node.parentNode,
111
110
  childNodes: [],
111
+ blockBehavior: null,
112
112
  isBogus: false,
113
113
  isFragment: false, // TODO: Case by case
114
114
  };
115
115
  replacementChildNodes.push(psNode);
116
116
  if (below) {
117
117
  const { line, column } = getPosition(node.raw, offset + raw.length);
118
- const token = parser.createToken(below, node.startOffset + offset + raw.length, node.startLine + line - 1, node.startCol + column - 1);
118
+ const token = parser.createToken(below, node.offset + offset + raw.length, node.line + line - 1, node.col + column - 1);
119
119
  const aboveNode = {
120
120
  ...token,
121
121
  nodeName: '#text',
@@ -144,8 +144,9 @@ ignoreBlock, throwErrorWhenTagHasUnresolved = true) {
144
144
  for (const tag of stack) {
145
145
  const raw = tag.startTag + tag.taggedCode + tag.endTag;
146
146
  const length = raw.length;
147
- if (attr.value.startOffset <= tag.index && tag.index + length <= attr.value.endOffset) {
148
- const offset = tag.index - attr.value.startOffset;
147
+ if (attr.value.offset <= tag.index &&
148
+ tag.index + length <= attr.value.offset + attr.value.raw.length) {
149
+ const offset = tag.index - attr.value.offset;
149
150
  const above = attr.value.raw.slice(0, offset);
150
151
  const below = attr.value.raw.slice(offset + length);
151
152
  parser.updateRaw(attr.value, above + raw + below);
@@ -162,7 +163,7 @@ ignoreBlock, throwErrorWhenTagHasUnresolved = true) {
162
163
  }
163
164
  // Update node raw
164
165
  const length = attr.raw.length;
165
- const offset = attr.startOffset - node.startOffset;
166
+ const offset = attr.offset - node.offset;
166
167
  const above = node.raw.slice(0, offset);
167
168
  const below = node.raw.slice(offset + length);
168
169
  parser.updateRaw(node, above + attr.raw + below);
package/lib/index.d.ts CHANGED
@@ -1,7 +1,8 @@
1
1
  export * from './debugger.js';
2
2
  export * from './enums.js';
3
+ export * from './get-namespace.js';
3
4
  export * from './idl-attributes.js';
4
5
  export * from './parser-error.js';
5
6
  export * from './parser.js';
6
7
  export * from './script-parser.js';
7
- export * from './types.js';
8
+ export type * from './types.js';
package/lib/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  export * from './debugger.js';
2
2
  export * from './enums.js';
3
+ export * from './get-namespace.js';
3
4
  export * from './idl-attributes.js';
4
5
  export * from './parser-error.js';
5
6
  export * from './parser.js';
6
7
  export * from './script-parser.js';
7
- export * from './types.js';
@@ -3,9 +3,12 @@
3
3
  * and raw text where the error was encountered.
4
4
  */
5
5
  export class ParserError extends Error {
6
+ col;
7
+ line;
8
+ name = 'ParserError';
9
+ raw;
6
10
  constructor(message, info) {
7
11
  super(message);
8
- this.name = 'ParserError';
9
12
  this.line = info.line ?? 1;
10
13
  this.col = info.col ?? 0;
11
14
  this.raw = info.raw ?? '';
@@ -17,12 +20,13 @@ export class ParserError extends Error {
17
20
  * the node name of the element that caused the error in the message.
18
21
  */
19
22
  export class TargetParserError extends ParserError {
23
+ name = 'TargetParserError';
24
+ nodeName;
20
25
  constructor(message, info) {
21
26
  const errMsg = info.nodeName
22
27
  ? `The ${info.nodeName} is invalid element (${info.line}:${info.col}): ${message}`
23
28
  : message;
24
29
  super(errMsg, info);
25
- this.name = 'TargetParserError';
26
30
  this.nodeName = info.nodeName ?? null;
27
31
  }
28
32
  }
@@ -31,12 +35,13 @@ export class TargetParserError extends ParserError {
31
35
  * including the file path in the error message for easier debugging.
32
36
  */
33
37
  export class ConfigParserError extends ParserError {
38
+ filePath;
39
+ name = 'ConfigParserError';
34
40
  constructor(message, info) {
35
41
  const pos = info.line != null && info.line != null ? `(${info.line}:${info.col})` : '';
36
42
  const file = ` in ${info.filePath}${pos}`;
37
43
  const errMsg = `${message}${file}`;
38
44
  super(errMsg, info);
39
- this.name = 'ConfigParserError';
40
45
  this.filePath = info.filePath;
41
46
  }
42
47
  }