svg-eslint-parser 0.2.0 → 0.3.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/dist/index.js CHANGED
@@ -1,10 +1,14 @@
1
- import { t as __exportAll } from "./chunk-D7D4PA-g.js";
1
+ import { t as __exportAll } from "./rolldown-runtime-D7D4PA-g.js";
2
+ import { isNumber } from "@ntnyq/utils";
2
3
  import { unionWith } from "eslint-visitor-keys";
3
4
  //#endregion
4
5
  //#region src/meta.ts
6
+ /**
7
+ * Package metadata exposed to parser consumers.
8
+ */
5
9
  const meta = {
6
10
  name: "svg-eslint-parser",
7
- version: "0.2.0"
11
+ version: "0.3.0"
8
12
  };
9
13
  //#endregion
10
14
  //#region src/parser/error.ts
@@ -15,12 +19,12 @@ var ParseError = class extends SyntaxError {
15
19
  index;
16
20
  lineNumber;
17
21
  column;
18
- constructor(message, offset, line, column) {
19
- super(message);
22
+ constructor(message, options, line, column) {
23
+ super(message, options);
20
24
  this.name = "ParseError";
21
- this.index = offset;
22
- this.lineNumber = line;
23
- this.column = column;
25
+ this.index = isNumber(options) ? options : options.offset;
26
+ this.lineNumber = isNumber(options) ? line ?? 0 : options.line;
27
+ this.column = isNumber(options) ? column ?? 0 : options.column;
24
28
  }
25
29
  };
26
30
  //#endregion
@@ -43,26 +47,29 @@ const XML_DECLARATION_START = "<?xml";
43
47
  const XML_DECLARATION_END = "?>";
44
48
  /**
45
49
  * regexp for open tag start
46
- * @regex101 https://regex101.com/?regex=%5E%3C%5Cw&flavor=javascript
50
+ * @regex101 https://regex101.com/?regex=%5E%3C%5Cw&flags=u&flavor=javascript
47
51
  */
48
52
  const RE_OPEN_TAG_START = /^<\w/u;
49
53
  /**
50
54
  * regexp for open tag name
51
- * @regex101 https://regex101.com/?regex=%5E%3C%28%5CS%2B%29&flavor=javascript
55
+ * @regex101 https://regex101.com/?regex=%5E%3C%28%3F%3CtagName%3E%5CS%2B%29&flags=u&flavor=javascript
52
56
  */
53
57
  const RE_OPEN_TAG_NAME = /^<(?<tagName>\S+)/u;
54
58
  /**
55
59
  * regexp for close tag name
56
- * @regex101 https://regex101.com/?regex=%5E%3C%5C%2F%28%28%3F%3A.%7C%5Cr%3F%5Cn%29*%29%3E%24&flavor=javascript
60
+ * @regex101 https://regex101.com/?regex=%5E%3C%5C%2F%28%3F%3CtagName%3E%28%3F%3A.%7C%5Cr%3F%5Cn%29*%29%3E%24&flags=u&flavor=javascript
57
61
  */
58
62
  const RE_CLOSE_TAG_NAME = /^<\/(?<tagName>(?:.|\r?\n)*)>$/u;
59
63
  /**
60
64
  * regexp for incomplete closing tag
61
- * @regex101 https://regex101.com/?regex=%3C%5C%2F%5B%5E%3E%5D%2B%24&flavor=javascript
65
+ * @regex101 https://regex101.com/?regex=%3C%5C%2F%5B%5E%3E%5D%2B%24&flags=u&flavor=javascript
62
66
  */
63
67
  const RE_INCOMPLETE_CLOSING_TAG = /<\/[^>]+$/u;
64
68
  //#endregion
65
69
  //#region src/constants/nodeTypes.ts
70
+ /**
71
+ * AST node type names emitted by the parser.
72
+ */
66
73
  let NodeTypes = /* @__PURE__ */ function(NodeTypes) {
67
74
  NodeTypes["Attribute"] = "Attribute";
68
75
  NodeTypes["AttributeKey"] = "AttributeKey";
@@ -84,6 +91,9 @@ let NodeTypes = /* @__PURE__ */ function(NodeTypes) {
84
91
  }({});
85
92
  //#endregion
86
93
  //#region src/constants/tokenTypes.ts
94
+ /**
95
+ * Token type names emitted by tokenizer contexts.
96
+ */
87
97
  let TokenTypes = /* @__PURE__ */ function(TokenTypes) {
88
98
  /**
89
99
  * @pg Content tokens
@@ -131,6 +141,9 @@ let TokenTypes = /* @__PURE__ */ function(TokenTypes) {
131
141
  }({});
132
142
  //#endregion
133
143
  //#region src/constants/specialChar.ts
144
+ /**
145
+ * Character constants used by tokenizer handlers.
146
+ */
134
147
  const SPECIAL_CHAR = {
135
148
  closingCorner: `>`,
136
149
  colon: `:`,
@@ -154,7 +167,7 @@ const SPECIAL_CHAR = {
154
167
  * @copyright {@link https://github.com/davidohlin/svg-elements}
155
168
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element}
156
169
  */
157
- const SVG_ELEMENTS = new Set([
170
+ const SVG_ELEMENTS = /* @__PURE__ */ new Set([
158
171
  "a",
159
172
  "animate",
160
173
  "animateMotion",
@@ -225,7 +238,7 @@ const SVG_ELEMENTS = new Set([
225
238
  *
226
239
  * @see {@link https://developer.mozilla.org/en-US/docs/Web/SVG/Element#obsolete_and_deprecated_elements}
227
240
  */
228
- const DEPRECATED_SVG_ELEMENTS = new Set([
241
+ const DEPRECATED_SVG_ELEMENTS = /* @__PURE__ */ new Set([
229
242
  "altGlyph",
230
243
  "altGlyphDef",
231
244
  "altGlyphItem",
@@ -247,7 +260,7 @@ const DEPRECATED_SVG_ELEMENTS = new Set([
247
260
  /**
248
261
  * self closing svg elements
249
262
  */
250
- const SELF_CLOSING_ELEMENTS = new Set([
263
+ const SELF_CLOSING_ELEMENTS = /* @__PURE__ */ new Set([
251
264
  "animate",
252
265
  "animateMotion",
253
266
  "animateTransform",
@@ -283,6 +296,9 @@ const SELF_CLOSING_ELEMENTS = new Set([
283
296
  ]);
284
297
  //#endregion
285
298
  //#region src/constants/tokenizerContextTypes.ts
299
+ /**
300
+ * Tokenizer state machine contexts.
301
+ */
286
302
  let TokenizerContextTypes = /* @__PURE__ */ function(TokenizerContextTypes) {
287
303
  TokenizerContextTypes["AttributeKey"] = "AttributeKey";
288
304
  TokenizerContextTypes["Attributes"] = "Attributes";
@@ -311,6 +327,9 @@ let TokenizerContextTypes = /* @__PURE__ */ function(TokenizerContextTypes) {
311
327
  }({});
312
328
  //#endregion
313
329
  //#region src/constants/constructTreeContextTypes.ts
330
+ /**
331
+ * AST construction state machine contexts.
332
+ */
314
333
  let ConstructTreeContextTypes = /* @__PURE__ */ function(ConstructTreeContextTypes) {
315
334
  ConstructTreeContextTypes["Attribute"] = "Attribute";
316
335
  ConstructTreeContextTypes["Attributes"] = "Attributes";
@@ -329,6 +348,78 @@ let ConstructTreeContextTypes = /* @__PURE__ */ function(ConstructTreeContextTyp
329
348
  return ConstructTreeContextTypes;
330
349
  }({});
331
350
  //#endregion
351
+ //#region src/parser/errorHandler.ts
352
+ /**
353
+ * Collects recoverable parse errors and warnings during parsing.
354
+ */
355
+ var ErrorHandler = class {
356
+ errors = [];
357
+ warnings = [];
358
+ addError(error) {
359
+ this.errors.push(error);
360
+ }
361
+ addWarning(warning) {
362
+ this.warnings.push(warning);
363
+ }
364
+ hasErrors() {
365
+ return this.errors.length > 0;
366
+ }
367
+ hasWarnings() {
368
+ return this.warnings.length > 0;
369
+ }
370
+ clear() {
371
+ this.errors = [];
372
+ this.warnings = [];
373
+ }
374
+ getErrors() {
375
+ return [...this.errors];
376
+ }
377
+ getWarnings() {
378
+ return [...this.warnings];
379
+ }
380
+ /**
381
+ * Merge errors from another ErrorHandler
382
+ */
383
+ mergeErrors(other) {
384
+ this.errors.push(...other.errors);
385
+ this.warnings.push(...other.warnings);
386
+ }
387
+ /**
388
+ * Get all issues (errors + warnings) sorted by position
389
+ */
390
+ getAllIssues() {
391
+ return [...this.errors, ...this.warnings].sort((a, b) => a.range[0] - b.range[0]);
392
+ }
393
+ /**
394
+ * Format errors for display
395
+ */
396
+ format() {
397
+ const issues = this.getAllIssues();
398
+ if (issues.length === 0) return "No errors or warnings";
399
+ return issues.map((issue) => {
400
+ const { loc, message, type } = issue;
401
+ return `${loc.start.line}:${loc.start.column} [${type}] ${message}`;
402
+ }).join("\n");
403
+ }
404
+ };
405
+ //#endregion
406
+ //#region src/types/errors.ts
407
+ /**
408
+ * Categories of recoverable parse diagnostics.
409
+ */
410
+ let ParseErrorType = /* @__PURE__ */ function(ParseErrorType) {
411
+ ParseErrorType["InvalidAttribute"] = "InvalidAttribute";
412
+ ParseErrorType["InvalidCharacter"] = "InvalidCharacter";
413
+ ParseErrorType["InvalidDoctypeAttribute"] = "InvalidDoctypeAttribute";
414
+ ParseErrorType["InvalidXMLDeclaration"] = "InvalidXMLDeclaration";
415
+ ParseErrorType["MalformedComment"] = "MalformedComment";
416
+ ParseErrorType["MismatchedTag"] = "MismatchedTag";
417
+ ParseErrorType["UnclosedTag"] = "UnclosedTag";
418
+ ParseErrorType["UnexpectedToken"] = "UnexpectedToken";
419
+ ParseErrorType["UnmatchedQuote"] = "UnmatchedQuote";
420
+ return ParseErrorType;
421
+ }({});
422
+ //#endregion
332
423
  //#region src/utils/cloneNode.ts
333
424
  /**
334
425
  * Deep clone a node, removing parent references
@@ -436,8 +527,14 @@ function clearParent(ast) {
436
527
  * @param code - Character code to check
437
528
  * @returns True if code is a newline character (LF, CR, LS, PS)
438
529
  */
530
+ const LINE_BREAK_CODES = /* @__PURE__ */ new Set([
531
+ 10,
532
+ 13,
533
+ 8232,
534
+ 8233
535
+ ]);
439
536
  function isNewLine(code) {
440
- return code === 10 || code === 13 || code === 8232 || code === 8233;
537
+ return LINE_BREAK_CODES.has(code);
441
538
  }
442
539
  /**
443
540
  * Find the next line break position in a string
@@ -472,6 +569,62 @@ function getLineInfo(input, offset) {
472
569
  }
473
570
  }
474
571
  //#endregion
572
+ //#region src/visitorKeys.ts
573
+ const keys = {
574
+ Program: ["document"],
575
+ Document: ["children"],
576
+ XMLDeclaration: ["attributes"],
577
+ XMLDeclarationAttribute: ["key", "value"],
578
+ XMLDeclarationAttributeKey: [],
579
+ XMLDeclarationAttributeValue: [],
580
+ Doctype: ["attributes"],
581
+ DoctypeAttribute: ["value"],
582
+ DoctypeAttributeValue: [],
583
+ Attribute: ["key", "value"],
584
+ AttributeKey: [],
585
+ AttributeValue: [],
586
+ Element: ["attributes", "children"],
587
+ Comment: [],
588
+ Text: [],
589
+ Error: []
590
+ };
591
+ let vistorKeysCache = null;
592
+ /**
593
+ * Get ESLint visitor keys extended with parser-specific AST nodes.
594
+ * @returns Cached visitor key map
595
+ */
596
+ function getVisitorKeys() {
597
+ if (!vistorKeysCache) {
598
+ const merged = unionWith(keys);
599
+ vistorKeysCache = {
600
+ ...merged,
601
+ Tag: merged.Element
602
+ };
603
+ }
604
+ return vistorKeysCache;
605
+ }
606
+ /**
607
+ * Shared visitor key map used by parser traversal and ESLint integration.
608
+ */
609
+ const visitorKeys = getVisitorKeys();
610
+ //#endregion
611
+ //#region src/utils/getChildNodes.ts
612
+ /**
613
+ * Get child AST nodes according to the parser visitor keys.
614
+ * @param node - Parent AST node
615
+ * @returns Child nodes in visitor-key order
616
+ */
617
+ function getChildNodes(node) {
618
+ const keys = visitorKeys[node.type] ?? [];
619
+ const children = [];
620
+ for (const key of keys) {
621
+ const value = node[key];
622
+ if (Array.isArray(value)) children.push(...value);
623
+ else if (value) children.push(value);
624
+ }
625
+ return children;
626
+ }
627
+ //#endregion
475
628
  //#region src/utils/nodeHelpers.ts
476
629
  /**
477
630
  * Filter nodes by a predicate function
@@ -483,8 +636,7 @@ function filterNodes(node, predicate) {
483
636
  const results = [];
484
637
  function traverse(currentNode) {
485
638
  if (predicate(currentNode)) results.push(currentNode);
486
- if ("children" in currentNode && Array.isArray(currentNode.children)) for (const child of currentNode.children) traverse(child);
487
- if ("attributes" in currentNode && Array.isArray(currentNode.attributes)) for (const attr of currentNode.attributes) traverse(attr);
639
+ for (const child of getChildNodes(currentNode)) traverse(child);
488
640
  }
489
641
  traverse(node);
490
642
  return results;
@@ -497,8 +649,11 @@ function filterNodes(node, predicate) {
497
649
  */
498
650
  function mapNodes(node, mapper) {
499
651
  const mapped = mapper(node);
500
- if ("children" in mapped && Array.isArray(mapped.children)) mapped.children = mapped.children.map((child) => mapNodes(child, mapper));
501
- if ("attributes" in mapped && Array.isArray(mapped.attributes)) mapped.attributes = mapped.attributes.map((attr) => mapNodes(attr, mapper));
652
+ for (const key of Object.keys(mapped)) {
653
+ const value = mapped[key];
654
+ if (Array.isArray(value)) mapped[key] = value.map((item) => item && typeof item === "object" && "type" in item ? mapNodes(item, mapper) : item);
655
+ else if (value && typeof value === "object" && "type" in value) mapped[key] = mapNodes(value, mapper);
656
+ }
502
657
  return mapped;
503
658
  }
504
659
  /**
@@ -536,8 +691,7 @@ function getNodeDepth(node) {
536
691
  */
537
692
  function countNodes(node) {
538
693
  let count = 1;
539
- if ("children" in node && Array.isArray(node.children)) for (const child of node.children) count += countNodes(child);
540
- if ("attributes" in node && Array.isArray(node.attributes)) for (const attr of node.attributes) count += countNodes(attr);
694
+ for (const child of getChildNodes(node)) count += countNodes(child);
541
695
  return count;
542
696
  }
543
697
  //#endregion
@@ -553,10 +707,7 @@ function traverseAST(node, visitor, parent = null) {
553
707
  if (visitor.enter) {
554
708
  if (visitor.enter(node, parent) === false) shouldTraverseChildren = false;
555
709
  }
556
- if (shouldTraverseChildren) {
557
- if ("children" in node && Array.isArray(node.children)) for (const child of node.children) traverseAST(child, visitor, node);
558
- if ("attributes" in node && Array.isArray(node.attributes)) for (const attr of node.attributes) traverseAST(attr, visitor, node);
559
- }
710
+ if (shouldTraverseChildren) for (const child of getChildNodes(node)) traverseAST(child, visitor, node);
560
711
  if (visitor.leave) visitor.leave(node, parent);
561
712
  }
562
713
  /**
@@ -569,13 +720,19 @@ function walkAST(node, callback) {
569
720
  }
570
721
  //#endregion
571
722
  //#region src/utils/isWhitespace.ts
723
+ const WHITESPACE_CHARS = /* @__PURE__ */ new Set([
724
+ SPECIAL_CHAR.space,
725
+ SPECIAL_CHAR.newline,
726
+ SPECIAL_CHAR.return,
727
+ SPECIAL_CHAR.tab
728
+ ]);
572
729
  /**
573
730
  * Check if a character is whitespace (space, newline, return, or tab)
574
731
  * @param char - Character to check
575
732
  * @returns True if character is whitespace
576
733
  */
577
734
  function isWhitespace(char) {
578
- return char === SPECIAL_CHAR.space || char === SPECIAL_CHAR.newline || char === SPECIAL_CHAR.return || char === SPECIAL_CHAR.tab;
735
+ return WHITESPACE_CHARS.has(char);
579
736
  }
580
737
  //#endregion
581
738
  //#region src/utils/validateNode.ts
@@ -691,8 +848,7 @@ function findNodeByType(node, type) {
691
848
  const results = [];
692
849
  function traverse(currentNode) {
693
850
  if (currentNode.type === type) results.push(currentNode);
694
- if ("children" in currentNode && Array.isArray(currentNode.children)) for (const child of currentNode.children) traverse(child);
695
- if ("attributes" in currentNode && Array.isArray(currentNode.attributes)) for (const attr of currentNode.attributes) traverse(attr);
851
+ for (const child of getChildNodes(currentNode)) traverse(child);
696
852
  }
697
853
  traverse(node);
698
854
  return results;
@@ -706,14 +862,10 @@ function findNodeByType(node, type) {
706
862
  function findFirstNodeByType(node, type) {
707
863
  function traverse(currentNode) {
708
864
  if (currentNode.type === type) return currentNode;
709
- if ("children" in currentNode && Array.isArray(currentNode.children)) for (const child of currentNode.children) {
865
+ for (const child of getChildNodes(currentNode)) {
710
866
  const result = traverse(child);
711
867
  if (result) return result;
712
868
  }
713
- if ("attributes" in currentNode && Array.isArray(currentNode.attributes)) for (const attr of currentNode.attributes) {
714
- const result = traverse(attr);
715
- if (result) return result;
716
- }
717
869
  }
718
870
  return traverse(node);
719
871
  }
@@ -777,11 +929,7 @@ function calculateTokenLocation(source, range) {
777
929
  * @returns Array containing [startPosition, endPosition + 1]
778
930
  */
779
931
  function calculateTokenCharactersRange(state, options) {
780
- const startPosition = state.sourceCode.index() - (state.accumulatedContent.length() - 1) - state.decisionBuffer.length();
781
- let endPosition = 0;
782
- if (options.keepBuffer) endPosition = state.sourceCode.index();
783
- else endPosition = state.sourceCode.index() - state.decisionBuffer.length();
784
- return [startPosition, endPosition + 1];
932
+ return [state.sourceCode.index() - (state.accumulatedContent.length() - 1) - state.decisionBuffer.length(), (options.keepBuffer ? state.sourceCode.index() : state.sourceCode.index() - state.decisionBuffer.length()) + 1];
785
933
  }
786
934
  //#endregion
787
935
  //#region src/utils/calculateTokenPosition.ts
@@ -830,7 +978,7 @@ const dispatch$13 = createTokenDispatcher([
830
978
  }
831
979
  },
832
980
  {
833
- tokenType: new Set(["AttributeKey", "AttributeAssignment"]),
981
+ tokenType: /* @__PURE__ */ new Set(["AttributeKey", "AttributeAssignment"]),
834
982
  handler(_, state) {
835
983
  state.currentContext = {
836
984
  parentRef: state.currentContext,
@@ -845,7 +993,7 @@ const dispatch$13 = createTokenDispatcher([
845
993
  const tagName = state.currentNode.name;
846
994
  state.currentNode.openEnd = createNodeFrom(token);
847
995
  updateNodeEnd(state.currentNode, token);
848
- if (tagName && SELF_CLOSING_ELEMENTS.has(tagName) && state.currentNode.openEnd.value === "/>") {
996
+ if (tagName && state.currentNode.openEnd.value === "/>") {
849
997
  state.currentNode.selfClosing = true;
850
998
  state.currentNode = state.currentNode.parentRef;
851
999
  state.currentContext = state.currentContext.parentRef;
@@ -876,6 +1024,9 @@ const dispatch$13 = createTokenDispatcher([
876
1024
  state.caretPosition++;
877
1025
  return state;
878
1026
  });
1027
+ /**
1028
+ * Construct an element node after its opening tag has started.
1029
+ */
879
1030
  function construct$13(token, state) {
880
1031
  return dispatch$13(token, state);
881
1032
  }
@@ -894,6 +1045,7 @@ const dispatch$12 = createTokenDispatcher([
894
1045
  tokenType: "CommentContent",
895
1046
  handler(token, state) {
896
1047
  state.currentNode.content = token.value;
1048
+ state.currentNode.value = token.value;
897
1049
  state.caretPosition++;
898
1050
  return state;
899
1051
  }
@@ -909,6 +1061,9 @@ const dispatch$12 = createTokenDispatcher([
909
1061
  }
910
1062
  }
911
1063
  ]);
1064
+ /**
1065
+ * Construct a comment node from comment tokens.
1066
+ */
912
1067
  function construct$12(token, state) {
913
1068
  return dispatch$12(token, state);
914
1069
  }
@@ -936,7 +1091,7 @@ const dispatch$11 = createTokenDispatcher([
936
1091
  }
937
1092
  },
938
1093
  {
939
- tokenType: new Set(["DoctypeAttributeWrapperStart", "DoctypeAttributeValue"]),
1094
+ tokenType: /* @__PURE__ */ new Set(["DoctypeAttributeWrapperStart", "DoctypeAttributeValue"]),
940
1095
  handler(_, state) {
941
1096
  state.currentContext = {
942
1097
  parentRef: state.currentContext,
@@ -949,6 +1104,9 @@ const dispatch$11 = createTokenDispatcher([
949
1104
  state.caretPosition++;
950
1105
  return state;
951
1106
  });
1107
+ /**
1108
+ * Construct a doctype node from doctype tokens.
1109
+ */
952
1110
  function construct$11(token, state) {
953
1111
  return dispatch$11(token, state);
954
1112
  }
@@ -967,6 +1125,9 @@ const dispatch$10 = createTokenDispatcher([{
967
1125
  state.caretPosition++;
968
1126
  return state;
969
1127
  });
1128
+ /**
1129
+ * Construct the normalized name for an element node.
1130
+ */
970
1131
  function construct$10(token, state) {
971
1132
  return dispatch$10(token, state);
972
1133
  }
@@ -975,7 +1136,7 @@ function construct$10(token, state) {
975
1136
  var attribute_exports = /* @__PURE__ */ __exportAll({ construct: () => construct$9 });
976
1137
  const dispatch$9 = createTokenDispatcher([
977
1138
  {
978
- tokenType: new Set(["OpenTagEnd"]),
1139
+ tokenType: /* @__PURE__ */ new Set(["OpenTagEnd"]),
979
1140
  handler(_, state) {
980
1141
  state.currentContext = state.currentContext.parentRef;
981
1142
  return state;
@@ -1013,6 +1174,9 @@ const dispatch$9 = createTokenDispatcher([
1013
1174
  state.caretPosition++;
1014
1175
  return state;
1015
1176
  });
1177
+ /**
1178
+ * Construct an element attribute from attribute tokens.
1179
+ */
1016
1180
  function construct$9(token, state) {
1017
1181
  return dispatch$9(token, state);
1018
1182
  }
@@ -1020,7 +1184,7 @@ function construct$9(token, state) {
1020
1184
  //#region src/constructor/handlers/attributes.ts
1021
1185
  var attributes_exports$1 = /* @__PURE__ */ __exportAll({ construct: () => construct$8 });
1022
1186
  const dispatch$8 = createTokenDispatcher([{
1023
- tokenType: new Set(["AttributeKey", "AttributeAssignment"]),
1187
+ tokenType: /* @__PURE__ */ new Set(["AttributeKey", "AttributeAssignment"]),
1024
1188
  handler(token, state) {
1025
1189
  initAttributesIfNone(state.currentNode);
1026
1190
  state.currentNode.attributes.push({
@@ -1035,7 +1199,7 @@ const dispatch$8 = createTokenDispatcher([{
1035
1199
  return state;
1036
1200
  }
1037
1201
  }, {
1038
- tokenType: new Set(["OpenTagEnd"]),
1202
+ tokenType: /* @__PURE__ */ new Set(["OpenTagEnd"]),
1039
1203
  handler(_, state) {
1040
1204
  state.currentContext = state.currentContext.parentRef;
1041
1205
  return state;
@@ -1044,13 +1208,49 @@ const dispatch$8 = createTokenDispatcher([{
1044
1208
  state.caretPosition++;
1045
1209
  return state;
1046
1210
  });
1211
+ /**
1212
+ * Construct the attributes collection for an element node.
1213
+ */
1047
1214
  function construct$8(token, state) {
1048
1215
  return dispatch$8(token, state);
1049
1216
  }
1050
1217
  //#endregion
1051
1218
  //#region src/constructor/handlers/tagContent.ts
1052
1219
  var tagContent_exports = /* @__PURE__ */ __exportAll({ construct: () => construct$7 });
1220
+ function addUnclosedTagError(state, node) {
1221
+ state.errorHandler.addError({
1222
+ type: "UnclosedTag",
1223
+ message: `Unclosed tag "${node.name ?? "unknown"}".`,
1224
+ range: cloneRange(node.range),
1225
+ loc: cloneLocation(node.loc),
1226
+ recovery: "The parser kept the partial element in the AST."
1227
+ });
1228
+ }
1053
1229
  const dispatch$7 = createTokenDispatcher([
1230
+ {
1231
+ tokenType: "XMLDeclarationOpen",
1232
+ handler(token, state) {
1233
+ if (state.currentNode.type !== "Document") {
1234
+ state.caretPosition++;
1235
+ return state;
1236
+ }
1237
+ initChildrenIfNone(state.currentNode);
1238
+ const declarationNode = {
1239
+ type: "XMLDeclaration",
1240
+ parentRef: state.currentNode,
1241
+ range: cloneRange(token.range),
1242
+ loc: cloneLocation(token.loc),
1243
+ attributes: []
1244
+ };
1245
+ state.currentNode.children.push(declarationNode);
1246
+ state.currentNode = declarationNode;
1247
+ state.currentContext = {
1248
+ parentRef: state.currentContext,
1249
+ type: "XMLDeclaration"
1250
+ };
1251
+ return state;
1252
+ }
1253
+ },
1054
1254
  {
1055
1255
  tokenType: "OpenTagStart",
1056
1256
  handler(token, state) {
@@ -1109,7 +1309,31 @@ const dispatch$7 = createTokenDispatcher([
1109
1309
  {
1110
1310
  tokenType: "CloseTag",
1111
1311
  handler(token, state) {
1112
- if (parseCloseTagName(token.value) !== state.currentNode.name) {
1312
+ const closeTagName = parseCloseTagName(token.value);
1313
+ if (closeTagName !== state.currentNode.name) {
1314
+ state.errorHandler.addError({
1315
+ type: "MismatchedTag",
1316
+ message: `Expected closing tag for "${state.currentNode.name ?? "unknown"}" but found "${closeTagName}".`,
1317
+ range: cloneRange(token.range),
1318
+ loc: cloneLocation(token.loc),
1319
+ recovery: "The mismatched closing tag was skipped."
1320
+ });
1321
+ let ancestor = state.currentNode.parentRef;
1322
+ let context = state.currentContext.parentRef?.parentRef;
1323
+ while (ancestor && ancestor.type !== "Document") {
1324
+ if (ancestor.name === closeTagName) {
1325
+ let unclosedNode = state.currentNode;
1326
+ while (unclosedNode && unclosedNode !== ancestor) {
1327
+ addUnclosedTagError(state, unclosedNode);
1328
+ unclosedNode = unclosedNode.parentRef;
1329
+ }
1330
+ state.currentNode = ancestor;
1331
+ state.currentContext = context;
1332
+ return state;
1333
+ }
1334
+ ancestor = ancestor.parentRef;
1335
+ context = context?.parentRef?.parentRef;
1336
+ }
1113
1337
  state.caretPosition++;
1114
1338
  return state;
1115
1339
  }
@@ -1140,6 +1364,9 @@ const dispatch$7 = createTokenDispatcher([
1140
1364
  state.caretPosition++;
1141
1365
  return state;
1142
1366
  });
1367
+ /**
1368
+ * Construct document or element children from content tokens.
1369
+ */
1143
1370
  function construct$7(token, state) {
1144
1371
  return dispatch$7(token, state);
1145
1372
  }
@@ -1148,7 +1375,7 @@ function construct$7(token, state) {
1148
1375
  var attributeValue_exports$1 = /* @__PURE__ */ __exportAll({ construct: () => construct$6 });
1149
1376
  const dispatch$6 = createTokenDispatcher([
1150
1377
  {
1151
- tokenType: new Set([
1378
+ tokenType: /* @__PURE__ */ new Set([
1152
1379
  "OpenTagEnd",
1153
1380
  "AttributeKey",
1154
1381
  "AttributeAssignment"
@@ -1193,6 +1420,9 @@ const dispatch$6 = createTokenDispatcher([
1193
1420
  state.caretPosition++;
1194
1421
  return state;
1195
1422
  });
1423
+ /**
1424
+ * Construct an element attribute value from value tokens.
1425
+ */
1196
1426
  function construct$6(token, state) {
1197
1427
  return dispatch$6(token, state);
1198
1428
  }
@@ -1200,10 +1430,12 @@ function construct$6(token, state) {
1200
1430
  //#region src/constructor/handlers/xmlDeclaration.ts
1201
1431
  var xmlDeclaration_exports = /* @__PURE__ */ __exportAll({ construct: () => construct$5 });
1202
1432
  const dispatch$5 = createTokenDispatcher([{
1203
- tokenType: "OpenTagStart",
1204
- handler(token, state) {
1205
- state.currentNode.name = parseOpenTagName(token.value);
1206
- state.currentContext = state.currentContext.parentRef;
1433
+ tokenType: "XMLDeclarationOpen",
1434
+ handler(_, state) {
1435
+ state.currentContext = {
1436
+ parentRef: state.currentContext,
1437
+ type: "XMLDeclarationAttributes"
1438
+ };
1207
1439
  state.caretPosition++;
1208
1440
  return state;
1209
1441
  }
@@ -1211,6 +1443,9 @@ const dispatch$5 = createTokenDispatcher([{
1211
1443
  state.caretPosition++;
1212
1444
  return state;
1213
1445
  });
1446
+ /**
1447
+ * Construct an XML declaration node after its opening token.
1448
+ */
1214
1449
  function construct$5(token, state) {
1215
1450
  return dispatch$5(token, state);
1216
1451
  }
@@ -1266,6 +1501,9 @@ const dispatch$4 = createTokenDispatcher([
1266
1501
  state.caretPosition++;
1267
1502
  return state;
1268
1503
  });
1504
+ /**
1505
+ * Construct a doctype attribute from value and wrapper tokens.
1506
+ */
1269
1507
  function construct$4(token, state) {
1270
1508
  return dispatch$4(token, state);
1271
1509
  }
@@ -1279,7 +1517,7 @@ const dispatch$3 = createTokenDispatcher([{
1279
1517
  return state;
1280
1518
  }
1281
1519
  }, {
1282
- tokenType: new Set(["DoctypeAttributeWrapperStart", "DoctypeAttributeValue"]),
1520
+ tokenType: /* @__PURE__ */ new Set(["DoctypeAttributeWrapperStart", "DoctypeAttributeValue"]),
1283
1521
  handler(token, state) {
1284
1522
  initAttributesIfNone(state.currentNode);
1285
1523
  state.currentNode.attributes.push({
@@ -1297,6 +1535,9 @@ const dispatch$3 = createTokenDispatcher([{
1297
1535
  state.caretPosition++;
1298
1536
  return state;
1299
1537
  });
1538
+ /**
1539
+ * Construct the attributes collection for a doctype node.
1540
+ */
1300
1541
  function construct$3(token, state) {
1301
1542
  return dispatch$3(token, state);
1302
1543
  }
@@ -1305,40 +1546,36 @@ function construct$3(token, state) {
1305
1546
  var xmlDeclarationAttribute_exports = /* @__PURE__ */ __exportAll({ construct: () => construct$2 });
1306
1547
  const dispatch$2 = createTokenDispatcher([
1307
1548
  {
1308
- tokenType: new Set([
1309
- "OpenTagEnd",
1310
- "AttributeKey",
1311
- "AttributeAssignment"
1312
- ]),
1549
+ tokenType: /* @__PURE__ */ new Set(["XMLDeclarationClose"]),
1313
1550
  handler(_, state) {
1314
1551
  state.currentContext = state.currentContext.parentRef;
1315
1552
  return state;
1316
1553
  }
1317
1554
  },
1318
1555
  {
1319
- tokenType: "AttributeValue",
1556
+ tokenType: "XMLDeclarationAttributeKey",
1320
1557
  handler(token, state) {
1321
1558
  const attribute = getLastAttribute(state);
1322
- attribute.value = createNodeFrom(token);
1323
- updateNodeEnd(attribute, token);
1324
- state.caretPosition++;
1325
- return state;
1326
- }
1327
- },
1328
- {
1329
- tokenType: "AttributeValueWrapperStart",
1330
- handler(token, state) {
1331
- const attribute = getLastAttribute(state);
1332
- attribute.quoteChar = token.value;
1333
- if (!attribute.key) attribute.range = cloneRange(token.range);
1559
+ if (attribute.key !== void 0 || attribute.value !== void 0) {
1560
+ state.currentContext = state.currentContext.parentRef;
1561
+ return state;
1562
+ }
1563
+ attribute.key = createNodeFrom(token);
1334
1564
  state.caretPosition++;
1335
1565
  return state;
1336
1566
  }
1337
1567
  },
1338
1568
  {
1339
- tokenType: "AttributeValueWrapperEnd",
1340
- handler(token, state) {
1341
- updateNodeEnd(getLastAttribute(state), token);
1569
+ tokenType: "XMLDeclarationAttributeAssignment",
1570
+ handler(_, state) {
1571
+ if (getLastAttribute(state).value !== void 0) {
1572
+ state.currentContext = state.currentContext.parentRef;
1573
+ return state;
1574
+ }
1575
+ state.currentContext = {
1576
+ parentRef: state.currentContext,
1577
+ type: "XMLDeclarationAttributeValue"
1578
+ };
1342
1579
  state.caretPosition++;
1343
1580
  return state;
1344
1581
  }
@@ -1347,6 +1584,9 @@ const dispatch$2 = createTokenDispatcher([
1347
1584
  state.caretPosition++;
1348
1585
  return state;
1349
1586
  });
1587
+ /**
1588
+ * Construct an XML declaration attribute key and value transition.
1589
+ */
1350
1590
  function construct$2(token, state) {
1351
1591
  return dispatch$2(token, state);
1352
1592
  }
@@ -1354,17 +1594,20 @@ function construct$2(token, state) {
1354
1594
  //#region src/constructor/handlers/xmlDeclarationAttributes.ts
1355
1595
  var xmlDeclarationAttributes_exports$1 = /* @__PURE__ */ __exportAll({ construct: () => construct$1 });
1356
1596
  const dispatch$1 = createTokenDispatcher([{
1357
- tokenType: "OpenTagEnd",
1358
- handler(_, state) {
1597
+ tokenType: "XMLDeclarationClose",
1598
+ handler(token, state) {
1599
+ updateNodeEnd(state.currentNode, token);
1600
+ state.currentNode = state.currentNode.parentRef;
1359
1601
  state.currentContext = state.currentContext.parentRef;
1602
+ state.caretPosition++;
1360
1603
  return state;
1361
1604
  }
1362
1605
  }, {
1363
- tokenType: new Set(["AttributeKey", "AttributeAssignment"]),
1606
+ tokenType: /* @__PURE__ */ new Set(["XMLDeclarationAttributeKey", "XMLDeclarationAttributeAssignment"]),
1364
1607
  handler(token, state) {
1365
1608
  initAttributesIfNone(state.currentNode);
1366
1609
  state.currentNode.attributes.push({
1367
- type: "Attribute",
1610
+ type: "XMLDeclarationAttribute",
1368
1611
  range: cloneRange(token.range),
1369
1612
  loc: cloneLocation(token.loc)
1370
1613
  });
@@ -1378,6 +1621,9 @@ const dispatch$1 = createTokenDispatcher([{
1378
1621
  state.caretPosition++;
1379
1622
  return state;
1380
1623
  });
1624
+ /**
1625
+ * Construct the attributes collection for an XML declaration node.
1626
+ */
1381
1627
  function construct$1(token, state) {
1382
1628
  return dispatch$1(token, state);
1383
1629
  }
@@ -1386,14 +1632,14 @@ function construct$1(token, state) {
1386
1632
  var xmlDeclarationAttributeValue_exports$1 = /* @__PURE__ */ __exportAll({ construct: () => construct });
1387
1633
  const dispatch = createTokenDispatcher([
1388
1634
  {
1389
- tokenType: "OpenTagEnd",
1635
+ tokenType: "XMLDeclarationClose",
1390
1636
  handler(_, state) {
1391
1637
  state.currentContext = state.currentContext.parentRef;
1392
1638
  return state;
1393
1639
  }
1394
1640
  },
1395
1641
  {
1396
- tokenType: "AttributeValue",
1642
+ tokenType: "XMLDeclarationAttributeValue",
1397
1643
  handler(token, state) {
1398
1644
  const attribute = getLastAttribute(state);
1399
1645
  if (attribute.value !== void 0) {
@@ -1407,7 +1653,7 @@ const dispatch = createTokenDispatcher([
1407
1653
  }
1408
1654
  },
1409
1655
  {
1410
- tokenType: "AttributeValueWrapperStart",
1656
+ tokenType: "XMLDeclarationAttributeValueWrapperStart",
1411
1657
  handler(token, state) {
1412
1658
  const attribute = getLastAttribute(state);
1413
1659
  if (attribute.value !== void 0) {
@@ -1421,7 +1667,7 @@ const dispatch = createTokenDispatcher([
1421
1667
  }
1422
1668
  },
1423
1669
  {
1424
- tokenType: "AttributeValueWrapperEnd",
1670
+ tokenType: "XMLDeclarationAttributeValueWrapperEnd",
1425
1671
  handler(token, state) {
1426
1672
  updateNodeEnd(getLastAttribute(state), token);
1427
1673
  state.currentContext = state.currentContext.parentRef;
@@ -1433,6 +1679,9 @@ const dispatch = createTokenDispatcher([
1433
1679
  state.caretPosition++;
1434
1680
  return state;
1435
1681
  });
1682
+ /**
1683
+ * Construct an XML declaration attribute value from value tokens.
1684
+ */
1436
1685
  function construct(token, state) {
1437
1686
  return dispatch(token, state);
1438
1687
  }
@@ -1475,6 +1724,24 @@ function processTokens(tokens, state, positionOffset) {
1475
1724
  }
1476
1725
  return state;
1477
1726
  }
1727
+ function reportUnclosedNodes(state) {
1728
+ let node = state.currentNode;
1729
+ while (node && node !== state.rootNode) {
1730
+ if (node.type === "Element" && !node.selfClosing && !node.close) state.errorHandler.addError({
1731
+ type: "UnclosedTag",
1732
+ message: `Unclosed tag "${node.name ?? "unknown"}".`,
1733
+ range: cloneRange(node.range),
1734
+ loc: cloneLocation(node.loc),
1735
+ recovery: "The parser kept the partial element in the AST."
1736
+ });
1737
+ node = node.parentRef;
1738
+ }
1739
+ }
1740
+ /**
1741
+ * Build the document AST from tokenizer output.
1742
+ * @param tokens - Tokens produced from SVG source
1743
+ * @returns Constructed AST, final constructor state, and recoverable diagnostics
1744
+ */
1478
1745
  function constructTree(tokens) {
1479
1746
  const rootContext = {
1480
1747
  type: "TagContent",
@@ -1499,48 +1766,84 @@ function constructTree(tokens) {
1499
1766
  caretPosition: 0,
1500
1767
  currentContext: rootContext,
1501
1768
  currentNode: rootNode,
1769
+ errorHandler: new ErrorHandler(),
1502
1770
  rootNode
1503
1771
  };
1504
1772
  const positionOffset = state.caretPosition;
1505
- processTokens(tokens, state, positionOffset);
1773
+ reportUnclosedNodes(processTokens(tokens, state, positionOffset));
1506
1774
  return {
1507
1775
  state,
1508
- ast: state.rootNode
1776
+ ast: state.rootNode,
1777
+ errors: state.errorHandler.getErrors(),
1778
+ warnings: state.errorHandler.getWarnings()
1509
1779
  };
1510
1780
  }
1511
1781
  //#endregion
1512
1782
  //#region src/tokenizer/charsBuffer.ts
1783
+ /**
1784
+ * Mutable buffer used by tokenizer handlers while deciding token boundaries.
1785
+ */
1513
1786
  var CharsBuffer = class {
1514
1787
  charsBuffer = [];
1788
+ /**
1789
+ * Append a character wrapper to the buffer.
1790
+ */
1515
1791
  concat(chars) {
1516
1792
  const theLast = this.last();
1517
- if (theLast) theLast.concat(chars);
1793
+ if (theLast) theLast.append(chars);
1518
1794
  else this.charsBuffer.push(chars);
1519
1795
  }
1796
+ /**
1797
+ * Append another buffer without merging its entries.
1798
+ */
1520
1799
  concatBuffer(buffer) {
1521
1800
  this.charsBuffer.push(...buffer.charsBuffer);
1522
1801
  }
1802
+ /**
1803
+ * Get the total buffered character length.
1804
+ */
1523
1805
  length() {
1524
1806
  return this.charsBuffer.map((chars) => chars.length()).reduce((a, b) => a + b, 0);
1525
1807
  }
1808
+ /**
1809
+ * Clear all buffered characters.
1810
+ */
1526
1811
  clear() {
1527
1812
  this.charsBuffer = [];
1528
1813
  }
1814
+ /**
1815
+ * Get the concatenated buffered string.
1816
+ */
1529
1817
  value() {
1530
1818
  return this.charsBuffer.map((chars) => chars.value).join("");
1531
1819
  }
1820
+ /**
1821
+ * Get the last buffered character wrapper.
1822
+ */
1532
1823
  last() {
1533
1824
  return last(this.charsBuffer);
1534
1825
  }
1826
+ /**
1827
+ * Get the first buffered character wrapper.
1828
+ */
1535
1829
  first() {
1536
1830
  return first(this.charsBuffer);
1537
1831
  }
1832
+ /**
1833
+ * Remove the last buffered entry.
1834
+ */
1538
1835
  removeLast() {
1539
1836
  this.charsBuffer.splice(-1, 1);
1540
1837
  }
1838
+ /**
1839
+ * Remove the first buffered entry.
1840
+ */
1541
1841
  removeFirst() {
1542
1842
  this.charsBuffer.splice(0, 1);
1543
1843
  }
1844
+ /**
1845
+ * Replace this buffer with a copy of another buffer.
1846
+ */
1544
1847
  replace(other) {
1545
1848
  this.charsBuffer = [...other.charsBuffer];
1546
1849
  }
@@ -1551,7 +1854,7 @@ var data_exports = /* @__PURE__ */ __exportAll({
1551
1854
  handleContentEnd: () => handleContentEnd,
1552
1855
  parse: () => parse$22
1553
1856
  });
1554
- const INCOMPLETE_DOCTYPE_CHARS = new Set([
1857
+ const INCOMPLETE_DOCTYPE_CHARS = /* @__PURE__ */ new Set([
1555
1858
  "<!",
1556
1859
  "<!D",
1557
1860
  "<!DO",
@@ -1560,6 +1863,17 @@ const INCOMPLETE_DOCTYPE_CHARS = new Set([
1560
1863
  "<!DOCTY",
1561
1864
  "<!DOCTYP"
1562
1865
  ]);
1866
+ const INCOMPLETE_COMMENT_START_CHARS = /* @__PURE__ */ new Set([
1867
+ SPECIAL_CHAR.openingCorner,
1868
+ "<!",
1869
+ "<!-"
1870
+ ]);
1871
+ const INCOMPLETE_XML_DECLARATION_START_CHARS = /* @__PURE__ */ new Set([
1872
+ SPECIAL_CHAR.openingCorner,
1873
+ "<?",
1874
+ "<?x",
1875
+ "<?xm"
1876
+ ]);
1563
1877
  function generateTextToken(state) {
1564
1878
  const position = calculateTokenPosition(state, { keepBuffer: false });
1565
1879
  return {
@@ -1609,17 +1923,28 @@ function parseDoctypeOpen(state) {
1609
1923
  }
1610
1924
  function parseXMLDeclarationOpen(state) {
1611
1925
  if (state.accumulatedContent.length() !== 0) state.tokens.push(generateTextToken(state));
1926
+ const range = [state.sourceCode.index() - 4, state.sourceCode.index() + 1];
1927
+ state.tokens.push({
1928
+ type: "XMLDeclarationOpen",
1929
+ value: state.decisionBuffer.value(),
1930
+ range,
1931
+ loc: state.sourceCode.getLocationOf(range)
1932
+ });
1612
1933
  state.accumulatedContent.clear();
1613
1934
  state.decisionBuffer.clear();
1614
1935
  state.currentContext = "XMLDeclarationAttributes";
1615
1936
  state.sourceCode.next();
1616
1937
  }
1938
+ /**
1939
+ * Tokenize top-level text and dispatch into markup-specific contexts.
1940
+ */
1617
1941
  function parse$22(chars, state) {
1618
1942
  const value = chars.value();
1619
1943
  if (value === "<?xml") return parseXMLDeclarationOpen(state);
1944
+ if (INCOMPLETE_XML_DECLARATION_START_CHARS.has(value)) return state.sourceCode.next();
1620
1945
  if (RE_OPEN_TAG_START.test(value)) return parseOpeningCornerBraceWithText(state);
1621
1946
  if (value === "</") return parseOpeningCornerBraceWithSlash(state);
1622
- if (value === SPECIAL_CHAR.openingCorner || value === "<!" || value === "<!-") return state.sourceCode.next();
1947
+ if (INCOMPLETE_COMMENT_START_CHARS.has(value)) return state.sourceCode.next();
1623
1948
  if (value === "<!--") return parseCommentOpen(state);
1624
1949
  if (isIncompleteDoctype(value)) return state.sourceCode.next();
1625
1950
  if (value.toUpperCase() === "<!DOCTYPE") return parseDoctypeOpen(state);
@@ -1627,6 +1952,9 @@ function parse$22(chars, state) {
1627
1952
  state.decisionBuffer.clear();
1628
1953
  state.sourceCode.next();
1629
1954
  }
1955
+ /**
1956
+ * Flush buffered text when the source ends in data context.
1957
+ */
1630
1958
  function handleContentEnd(state) {
1631
1959
  const textContent = state.accumulatedContent.value() + state.decisionBuffer.value();
1632
1960
  if (textContent.length !== 0) {
@@ -1655,6 +1983,9 @@ function parseClosingCornerBrace$5(state) {
1655
1983
  state.currentContext = "Data";
1656
1984
  state.sourceCode.next();
1657
1985
  }
1986
+ /**
1987
+ * Tokenize a closing tag.
1988
+ */
1658
1989
  function parse$21(chars, state) {
1659
1990
  if (chars.value() === SPECIAL_CHAR.closingCorner) return parseClosingCornerBrace$5(state);
1660
1991
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -1672,7 +2003,7 @@ function parseTagEnd$3(state) {
1672
2003
  state.contextParams["OpenTagEnd"] = { tagName };
1673
2004
  state.contextParams["Attributes"] = void 0;
1674
2005
  }
1675
- function parseEqual(state) {
2006
+ function parseEqual$1(state) {
1676
2007
  const position = calculateTokenPosition(state, { keepBuffer: true });
1677
2008
  state.tokens.push({
1678
2009
  type: "AttributeAssignment",
@@ -1685,17 +2016,20 @@ function parseEqual(state) {
1685
2016
  state.currentContext = "AttributeValue";
1686
2017
  state.sourceCode.next();
1687
2018
  }
1688
- function parseNoneWhitespace(chars, state) {
2019
+ function parseNoneWhitespace$1(chars, state) {
1689
2020
  state.accumulatedContent.replace(state.decisionBuffer);
1690
2021
  state.currentContext = "AttributeKey";
1691
2022
  state.decisionBuffer.clear();
1692
2023
  state.sourceCode.next();
1693
2024
  }
2025
+ /**
2026
+ * Tokenize whitespace, assignments, and attribute starts inside an open tag.
2027
+ */
1694
2028
  function parse$20(chars, state) {
1695
2029
  const value = chars.value();
1696
2030
  if (value === SPECIAL_CHAR.closingCorner || value === SPECIAL_CHAR.slash) return parseTagEnd$3(state);
1697
- if (value === SPECIAL_CHAR.equal) return parseEqual(state);
1698
- if (!isWhitespace(value)) return parseNoneWhitespace(chars, state);
2031
+ if (value === SPECIAL_CHAR.equal) return parseEqual$1(state);
2032
+ if (!isWhitespace(value)) return parseNoneWhitespace$1(chars, state);
1699
2033
  state.decisionBuffer.clear();
1700
2034
  state.sourceCode.next();
1701
2035
  }
@@ -1716,6 +2050,9 @@ function parseClosingCornerBrace$4(state) {
1716
2050
  state.sourceCode.next();
1717
2051
  state.contextParams["OpenTagEnd"] = void 0;
1718
2052
  }
2053
+ /**
2054
+ * Tokenize the end of an opening tag.
2055
+ */
1719
2056
  function parse$19(chars, state) {
1720
2057
  if (chars.value() === SPECIAL_CHAR.closingCorner) return parseClosingCornerBrace$4(state);
1721
2058
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -1746,6 +2083,9 @@ function parseClosingCornerBrace$3(state) {
1746
2083
  state.decisionBuffer.clear();
1747
2084
  state.currentContext = "DoctypeClose";
1748
2085
  }
2086
+ /**
2087
+ * Tokenize the opening delimiter of a doctype declaration.
2088
+ */
1749
2089
  function parse$18(chars, state) {
1750
2090
  const value = chars.value();
1751
2091
  if (isWhitespace(value)) return parseWhitespace$1(state);
@@ -1772,6 +2112,9 @@ function parseKeyEnd$1(state) {
1772
2112
  state.decisionBuffer.clear();
1773
2113
  state.currentContext = "Attributes";
1774
2114
  }
2115
+ /**
2116
+ * Tokenize an element attribute key.
2117
+ */
1775
2118
  function parse$17(chars, state) {
1776
2119
  if (isKeyBreak$1(chars)) return parseKeyEnd$1(state);
1777
2120
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -1781,6 +2124,9 @@ function parse$17(chars, state) {
1781
2124
  //#endregion
1782
2125
  //#region src/tokenizer/handlers/doctypeClose.ts
1783
2126
  var doctypeClose_exports = /* @__PURE__ */ __exportAll({ parse: () => parse$16 });
2127
+ /**
2128
+ * Tokenize the closing delimiter of a doctype declaration.
2129
+ */
1784
2130
  function parse$16(chars, state) {
1785
2131
  const position = calculateTokenPosition(state, { keepBuffer: true });
1786
2132
  state.tokens.push({
@@ -1826,6 +2172,9 @@ function parseWhitespace(state) {
1826
2172
  state.contextParams["Attributes"] = { tagName };
1827
2173
  state.sourceCode.next();
1828
2174
  }
2175
+ /**
2176
+ * Tokenize the start of an opening tag.
2177
+ */
1829
2178
  function parse$15(chars, state) {
1830
2179
  const value = chars.value();
1831
2180
  if (value === SPECIAL_CHAR.closingCorner || value === SPECIAL_CHAR.slash) return parseTagEnd$2(state);
@@ -1863,6 +2212,9 @@ function parseBare$2(state) {
1863
2212
  state.currentContext = "AttributeValueBare";
1864
2213
  state.sourceCode.next();
1865
2214
  }
2215
+ /**
2216
+ * Tokenize the start of an element attribute value.
2217
+ */
1866
2218
  function parse$14(chars, state) {
1867
2219
  const value = chars.value();
1868
2220
  if (value === SPECIAL_CHAR.doubleQuote || value === SPECIAL_CHAR.singleQuote) return parseWrapper$5(state);
@@ -1894,6 +2246,9 @@ function parseCommentClose(state) {
1894
2246
  state.currentContext = "Data";
1895
2247
  state.sourceCode.next();
1896
2248
  }
2249
+ /**
2250
+ * Tokenize comment content until the closing delimiter.
2251
+ */
1897
2252
  function parse$13(chars, state) {
1898
2253
  const value = chars.value();
1899
2254
  if (value === SPECIAL_CHAR.hyphen || value === "--") return state.sourceCode.next();
@@ -1931,6 +2286,9 @@ function parseBare$1(state) {
1931
2286
  state.currentContext = "DoctypeAttributeBare";
1932
2287
  state.sourceCode.next();
1933
2288
  }
2289
+ /**
2290
+ * Tokenize doctype attributes and the doctype close transition.
2291
+ */
1934
2292
  function parse$12(chars, state) {
1935
2293
  const value = chars.value();
1936
2294
  if (value === SPECIAL_CHAR.doubleQuote || value === SPECIAL_CHAR.singleQuote) return parseWrapper$4(state);
@@ -1954,6 +2312,9 @@ function parseValueEnd(state) {
1954
2312
  state.decisionBuffer.clear();
1955
2313
  state.currentContext = "Attributes";
1956
2314
  }
2315
+ /**
2316
+ * Tokenize an unquoted element attribute value.
2317
+ */
1957
2318
  function parse$11(chars, state) {
1958
2319
  const value = chars.value();
1959
2320
  if (isWhitespace(value) || value === SPECIAL_CHAR.closingCorner || value === SPECIAL_CHAR.slash) return parseValueEnd(state);
@@ -1978,6 +2339,9 @@ function parseClosingCornerBrace$1(state) {
1978
2339
  state.sourceCode.next();
1979
2340
  state.contextParams["OpenTagEnd"] = void 0;
1980
2341
  }
2342
+ /**
2343
+ * Tokenize the opening delimiter of an XML declaration.
2344
+ */
1981
2345
  function parse$10(chars, state) {
1982
2346
  if (chars.value() === SPECIAL_CHAR.closingCorner) return parseClosingCornerBrace$1(state);
1983
2347
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -2001,6 +2365,9 @@ function parseClosingCornerBrace(state) {
2001
2365
  state.sourceCode.next();
2002
2366
  state.contextParams["OpenTagEnd"] = void 0;
2003
2367
  }
2368
+ /**
2369
+ * Tokenize the closing delimiter of an XML declaration.
2370
+ */
2004
2371
  function parse$9(chars, state) {
2005
2372
  if (chars.value() === SPECIAL_CHAR.closingCorner) return parseClosingCornerBrace(state);
2006
2373
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -2022,6 +2389,9 @@ function parseAttributeEnd(state) {
2022
2389
  state.decisionBuffer.clear();
2023
2390
  state.currentContext = "DoctypeAttributes";
2024
2391
  }
2392
+ /**
2393
+ * Tokenize an unquoted doctype attribute value.
2394
+ */
2025
2395
  function parse$8(chars, state) {
2026
2396
  const value = chars.value();
2027
2397
  if (isWhitespace(value) || value === SPECIAL_CHAR.closingCorner) return parseAttributeEnd(state);
@@ -2054,6 +2424,9 @@ function parseWrapper$3(state) {
2054
2424
  state.sourceCode.next();
2055
2425
  state.contextParams["AttributeValueWrapped"] = void 0;
2056
2426
  }
2427
+ /**
2428
+ * Tokenize a quoted element attribute value.
2429
+ */
2057
2430
  function parse$7(chars, state) {
2058
2431
  const wrapperChar = state.contextParams["AttributeValueWrapped"]?.wrapper;
2059
2432
  if (chars.value() === wrapperChar) return parseWrapper$3(state);
@@ -2086,6 +2459,9 @@ function parseWrapper$2(state) {
2086
2459
  state.sourceCode.next();
2087
2460
  state.contextParams["DoctypeAttributeWrapped"] = void 0;
2088
2461
  }
2462
+ /**
2463
+ * Tokenize a quoted doctype attribute value.
2464
+ */
2089
2465
  function parse$6(chars, state) {
2090
2466
  if (chars.value() === state.contextParams["DoctypeAttributeWrapped"]?.wrapper) return parseWrapper$2(state);
2091
2467
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -2109,11 +2485,34 @@ function parseXMLDeclarationClose(state) {
2109
2485
  state.currentContext = "Data";
2110
2486
  state.sourceCode.next();
2111
2487
  }
2488
+ function parseEqual(state) {
2489
+ const position = calculateTokenPosition(state, { keepBuffer: true });
2490
+ state.tokens.push({
2491
+ type: "XMLDeclarationAttributeAssignment",
2492
+ value: state.decisionBuffer.value(),
2493
+ range: position.range,
2494
+ loc: position.loc
2495
+ });
2496
+ state.accumulatedContent.clear();
2497
+ state.decisionBuffer.clear();
2498
+ state.currentContext = "XMLDeclarationAttributeValue";
2499
+ state.sourceCode.next();
2500
+ }
2501
+ function parseNoneWhitespace(state) {
2502
+ state.accumulatedContent.replace(state.decisionBuffer);
2503
+ state.currentContext = "XMLDeclarationAttributeKey";
2504
+ state.decisionBuffer.clear();
2505
+ state.sourceCode.next();
2506
+ }
2507
+ /**
2508
+ * Tokenize XML declaration attributes and the declaration close transition.
2509
+ */
2112
2510
  function parse$5(chars, state) {
2113
2511
  const value = chars.value();
2114
- if (value === SPECIAL_CHAR.question || value === SPECIAL_CHAR.closingCorner) return parseXMLDeclarationClose(state);
2512
+ if (value === SPECIAL_CHAR.question) return state.sourceCode.next();
2115
2513
  if (value === "?>") return parseXMLDeclarationClose(state);
2116
- state.accumulatedContent.concatBuffer(state.decisionBuffer);
2514
+ if (value === SPECIAL_CHAR.equal) return parseEqual(state);
2515
+ if (!isWhitespace(value)) return parseNoneWhitespace(state);
2117
2516
  state.decisionBuffer.clear();
2118
2517
  state.sourceCode.next();
2119
2518
  }
@@ -2136,6 +2535,9 @@ function parseKeyEnd(state) {
2136
2535
  state.decisionBuffer.clear();
2137
2536
  state.currentContext = "XMLDeclarationAttributes";
2138
2537
  }
2538
+ /**
2539
+ * Tokenize an XML declaration attribute key.
2540
+ */
2139
2541
  function parse$4(chars, state) {
2140
2542
  if (isKeyBreak(chars)) return parseKeyEnd(state);
2141
2543
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
@@ -2149,28 +2551,38 @@ function parseWrapper$1(state) {
2149
2551
  const wrapper = state.decisionBuffer.value();
2150
2552
  const range = [state.sourceCode.index(), state.sourceCode.index() + 1];
2151
2553
  state.tokens.push({
2152
- type: "AttributeValueWrapperStart",
2554
+ type: "XMLDeclarationAttributeValueWrapperStart",
2153
2555
  value: wrapper,
2154
2556
  range,
2155
2557
  loc: state.sourceCode.getLocationOf(range)
2156
2558
  });
2157
2559
  state.accumulatedContent.clear();
2158
2560
  state.decisionBuffer.clear();
2159
- state.currentContext = "AttributeValueWrapped";
2160
- state.contextParams["AttributeValueWrapped"] = { wrapper };
2561
+ state.currentContext = "XMLDeclarationAttributeValueWrapped";
2562
+ state.contextParams["XMLDeclarationAttributeValueWrapped"] = { wrapper };
2161
2563
  state.sourceCode.next();
2162
2564
  }
2163
2565
  function parseTagEnd(state) {
2164
2566
  state.accumulatedContent.clear();
2165
2567
  state.decisionBuffer.clear();
2166
- state.currentContext = "Attributes";
2568
+ state.currentContext = "XMLDeclarationAttributes";
2167
2569
  }
2168
2570
  function parseBare(state) {
2169
- state.accumulatedContent.replace(state.decisionBuffer);
2571
+ const range = [state.sourceCode.index(), state.sourceCode.index() + 1];
2572
+ state.tokens.push({
2573
+ type: "XMLDeclarationAttributeValue",
2574
+ value: state.decisionBuffer.value(),
2575
+ range,
2576
+ loc: state.sourceCode.getLocationOf(range)
2577
+ });
2578
+ state.accumulatedContent.clear();
2170
2579
  state.decisionBuffer.clear();
2171
- state.currentContext = "AttributeValueBare";
2580
+ state.currentContext = "XMLDeclarationAttributes";
2172
2581
  state.sourceCode.next();
2173
2582
  }
2583
+ /**
2584
+ * Tokenize the start of an XML declaration attribute value.
2585
+ */
2174
2586
  function parse$3(chars, state) {
2175
2587
  const value = chars.value();
2176
2588
  if (value === SPECIAL_CHAR.doubleQuote || value === SPECIAL_CHAR.singleQuote) return parseWrapper$1(state);
@@ -2186,26 +2598,29 @@ function parseWrapper(state) {
2186
2598
  const position = calculateTokenPosition(state, { keepBuffer: false });
2187
2599
  const endWrapperPosition = position.range[1];
2188
2600
  state.tokens.push({
2189
- type: "AttributeValue",
2601
+ type: "XMLDeclarationAttributeValue",
2190
2602
  value: state.accumulatedContent.value(),
2191
2603
  range: position.range,
2192
2604
  loc: position.loc
2193
2605
  });
2194
2606
  const range = [endWrapperPosition, endWrapperPosition + 1];
2195
2607
  state.tokens.push({
2196
- type: "AttributeValueWrapperEnd",
2608
+ type: "XMLDeclarationAttributeValueWrapperEnd",
2197
2609
  value: state.decisionBuffer.value(),
2198
2610
  range,
2199
2611
  loc: state.sourceCode.getLocationOf(range)
2200
2612
  });
2201
2613
  state.accumulatedContent.clear();
2202
2614
  state.decisionBuffer.clear();
2203
- state.currentContext = "Attributes";
2615
+ state.currentContext = "XMLDeclarationAttributes";
2204
2616
  state.sourceCode.next();
2205
- state.contextParams["AttributeValueWrapped"] = void 0;
2617
+ state.contextParams["XMLDeclarationAttributeValueWrapped"] = void 0;
2206
2618
  }
2619
+ /**
2620
+ * Tokenize a quoted XML declaration attribute value.
2621
+ */
2207
2622
  function parse$2(chars, state) {
2208
- const wrapperChar = state.contextParams["AttributeValueWrapped"]?.wrapper;
2623
+ const wrapperChar = state.contextParams["XMLDeclarationAttributeValueWrapped"]?.wrapper;
2209
2624
  if (chars.value() === wrapperChar) return parseWrapper(state);
2210
2625
  state.accumulatedContent.concatBuffer(state.decisionBuffer);
2211
2626
  state.decisionBuffer.clear();
@@ -2213,9 +2628,15 @@ function parse$2(chars, state) {
2213
2628
  }
2214
2629
  //#endregion
2215
2630
  //#region src/tokenizer/handlers/index.ts
2631
+ /**
2632
+ * Tokenizer handler for contexts that intentionally consume no content.
2633
+ */
2216
2634
  const noop = { parse: () => {} };
2217
2635
  //#endregion
2218
2636
  //#region src/tokenizer/chars.ts
2637
+ /**
2638
+ * Buffered source characters with their source range.
2639
+ */
2219
2640
  var Chars = class {
2220
2641
  value;
2221
2642
  range;
@@ -2223,19 +2644,31 @@ var Chars = class {
2223
2644
  this.value = value;
2224
2645
  this.range = range;
2225
2646
  }
2226
- concat(chars) {
2647
+ /**
2648
+ * Append adjacent characters into this range.
2649
+ */
2650
+ append(chars) {
2227
2651
  this.value += chars.value;
2228
2652
  this.range[1] = chars.range[1];
2229
2653
  }
2654
+ /**
2655
+ * Compare the buffered value with raw text.
2656
+ */
2230
2657
  equals(chars) {
2231
2658
  return this.value === chars;
2232
2659
  }
2660
+ /**
2661
+ * Get the buffered string length.
2662
+ */
2233
2663
  length() {
2234
2664
  return this.value.length;
2235
2665
  }
2236
2666
  };
2237
2667
  //#endregion
2238
2668
  //#region src/tokenizer/sourceCode.ts
2669
+ /**
2670
+ * Cursor-based view over source text for tokenizer handlers.
2671
+ */
2239
2672
  var SourceCode = class {
2240
2673
  source;
2241
2674
  charsList;
@@ -2244,24 +2677,42 @@ var SourceCode = class {
2244
2677
  this.source = source;
2245
2678
  this.charsList = this.createCharsList();
2246
2679
  }
2680
+ /**
2681
+ * Convert a character range to source locations.
2682
+ */
2247
2683
  getLocationOf(range) {
2248
2684
  return {
2249
2685
  start: getLineInfo(this.source, range[0]),
2250
2686
  end: getLineInfo(this.source, range[1])
2251
2687
  };
2252
2688
  }
2689
+ /**
2690
+ * Get the current character wrapper.
2691
+ */
2253
2692
  current() {
2254
2693
  return this.charsList[this.charsIndex];
2255
2694
  }
2695
+ /**
2696
+ * Advance to the next character.
2697
+ */
2256
2698
  next() {
2257
2699
  this.charsIndex++;
2258
2700
  }
2701
+ /**
2702
+ * Move back to the previous character.
2703
+ */
2259
2704
  prev() {
2260
2705
  this.charsIndex--;
2261
2706
  }
2707
+ /**
2708
+ * Check whether the cursor has reached the end of source.
2709
+ */
2262
2710
  isEof() {
2263
2711
  return this.charsIndex >= this.charsList.length;
2264
2712
  }
2713
+ /**
2714
+ * Get the current zero-based character index.
2715
+ */
2265
2716
  index() {
2266
2717
  return this.current().range[1] - 1;
2267
2718
  }
@@ -2312,6 +2763,11 @@ function tokenizeChars(state) {
2312
2763
  state.sourceCode.prev();
2313
2764
  if (handler.handleContentEnd !== void 0) handler.handleContentEnd(state);
2314
2765
  }
2766
+ /**
2767
+ * Tokenize SVG source into parser tokens.
2768
+ * @param source - SVG source code
2769
+ * @returns Tokenizer state and emitted tokens
2770
+ */
2315
2771
  function tokenize(source) {
2316
2772
  const tokens = [];
2317
2773
  const state = {
@@ -2332,48 +2788,29 @@ function tokenize(source) {
2332
2788
  }
2333
2789
  //#endregion
2334
2790
  //#region src/parser/parse.ts
2791
+ /**
2792
+ * Parse SVG source into a document AST and token list.
2793
+ * @param source - SVG source code
2794
+ * @param _options - Parser options reserved for API compatibility
2795
+ * @returns Direct parser result with document AST, tokens, and diagnostics
2796
+ */
2335
2797
  function parse$1(source, _options = {}) {
2336
2798
  const { tokens } = tokenize(source);
2337
- const { ast } = constructTree(tokens);
2799
+ const { ast, errors, warnings } = constructTree(tokens);
2338
2800
  return {
2339
2801
  ast: clearParent(ast),
2340
- tokens
2802
+ errors,
2803
+ tokens,
2804
+ warnings
2341
2805
  };
2342
2806
  }
2343
2807
  //#endregion
2344
- //#region src/visitorKeys.ts
2345
- const keys = {
2346
- Program: ["document"],
2347
- Document: ["children"],
2348
- XMLDeclaration: ["attributes"],
2349
- XMLDeclarationAttribute: ["key", "value"],
2350
- XMLDeclarationAttributeKey: [],
2351
- XMLDeclarationAttributeValue: [],
2352
- Doctype: ["attributes"],
2353
- DoctypeAttribute: ["value"],
2354
- DoctypeAttributeValue: [],
2355
- Attribute: ["key", "value"],
2356
- AttributeKey: [],
2357
- AttributeValue: [],
2358
- Element: ["attributes", "children"],
2359
- Comment: [],
2360
- Text: [],
2361
- Error: []
2362
- };
2363
- let vistorKeysCache = null;
2364
- function getVisitorKeys() {
2365
- if (!vistorKeysCache) {
2366
- const merged = unionWith(keys);
2367
- vistorKeysCache = {
2368
- ...merged,
2369
- Tag: merged.Element
2370
- };
2371
- }
2372
- return vistorKeysCache;
2373
- }
2374
- const visitorKeys = getVisitorKeys();
2375
- //#endregion
2376
2808
  //#region src/parser/traverse.ts
2809
+ /**
2810
+ * Walk an AST node tree using the parser visitor keys.
2811
+ * @param node - Root node to visit
2812
+ * @param visitor - Callback invoked for each reachable node
2813
+ */
2377
2814
  function traverse(node, visitor) {
2378
2815
  if (!node) return;
2379
2816
  visitor(node);
@@ -2387,8 +2824,14 @@ function traverse(node, visitor) {
2387
2824
  }
2388
2825
  //#endregion
2389
2826
  //#region src/parser/parseForESLint.ts
2827
+ /**
2828
+ * Parse SVG source into an ESLint-compatible parser result.
2829
+ * @param source - SVG source code
2830
+ * @param options - Parser options forwarded to the base parser
2831
+ * @returns ESLint parser result with visitor keys, services, tokens, and comments
2832
+ */
2390
2833
  function parseForESLint(source, options = {}) {
2391
- const { ast, tokens } = parse$1(source, options);
2834
+ const { ast, errors, tokens, warnings } = parse$1(source, options);
2392
2835
  const programNode = {
2393
2836
  type: "Program",
2394
2837
  body: [],
@@ -2412,15 +2855,31 @@ function parseForESLint(source, options = {}) {
2412
2855
  ast: programNode,
2413
2856
  visitorKeys,
2414
2857
  scopeManager: null,
2415
- services: { isSVG: true }
2858
+ services: {
2859
+ errors,
2860
+ isSVG: true,
2861
+ warnings
2862
+ }
2416
2863
  };
2417
2864
  }
2418
2865
  //#endregion
2419
2866
  //#region src/index.ts
2867
+ /**
2868
+ * Parser package name.
2869
+ */
2420
2870
  const name = meta.name;
2871
+ /**
2872
+ * ESLint visitor keys for parser-specific nodes.
2873
+ */
2421
2874
  const VisitorKeys = visitorKeys;
2875
+ /**
2876
+ * Parse SVG source and return the document node directly.
2877
+ * @param code - SVG source code
2878
+ * @param options - Parser options
2879
+ * @returns Parsed SVG document node
2880
+ */
2422
2881
  function parse(code, options = {}) {
2423
2882
  return parseForESLint(code, options).ast.document;
2424
2883
  }
2425
2884
  //#endregion
2426
- export { COMMENT_END, COMMENT_START, ConstructTreeContextTypes, DEPRECATED_SVG_ELEMENTS, NodeTypes, ParseError, RE_CLOSE_TAG_NAME, RE_INCOMPLETE_CLOSING_TAG, RE_OPEN_TAG_NAME, RE_OPEN_TAG_START, SELF_CLOSING_ELEMENTS, SPECIAL_CHAR, SVG_ELEMENTS, TokenTypes, TokenizerContextTypes, VisitorKeys, XML_DECLARATION_END, XML_DECLARATION_START, cloneNode, cloneNodeWithParent, countNodes, filterNodes, findFirstNodeByType, findNodeByType, getNodeDepth, getParentChain, isNodeType, mapNodes, meta, name, parse, parseForESLint, traverseAST, validateNode, walkAST };
2885
+ export { COMMENT_END, COMMENT_START, ConstructTreeContextTypes, DEPRECATED_SVG_ELEMENTS, NodeTypes, ParseError, ParseErrorType, RE_CLOSE_TAG_NAME, RE_INCOMPLETE_CLOSING_TAG, RE_OPEN_TAG_NAME, RE_OPEN_TAG_START, SELF_CLOSING_ELEMENTS, SPECIAL_CHAR, SVG_ELEMENTS, TokenTypes, TokenizerContextTypes, VisitorKeys, XML_DECLARATION_END, XML_DECLARATION_START, cloneNode, cloneNodeWithParent, countNodes, filterNodes, findFirstNodeByType, findNodeByType, getNodeDepth, getParentChain, isNodeType, mapNodes, meta, name, parse, parseForESLint, traverseAST, validateNode, walkAST };