astro-eslint-parser 2.1.0 → 3.0.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.
Files changed (3) hide show
  1. package/lib/index.d.mts +113 -18
  2. package/lib/index.mjs +871 -1172
  3. package/package.json +7 -9
package/lib/index.mjs CHANGED
@@ -11,8 +11,7 @@ import semver, { satisfies } from "semver";
11
11
  import { globSync, isDynamicPattern } from "tinyglobby";
12
12
  import { Reference, Variable, analyze } from "@typescript-eslint/scope-manager";
13
13
  import * as eslintScope$1 from "eslint-scope";
14
- import { DecodingMode, EntityDecoder, htmlDecodeTree } from "entities/decode";
15
- import * as service from "astrojs-compiler-sync";
14
+ import { parse } from "@astrojs/compiler-rs";
16
15
  const KEYS = unionWith({
17
16
  Program: ["body"],
18
17
  AstroFragment: ["children"],
@@ -62,13 +61,15 @@ function getTSProgram(code, options, ts) {
62
61
  return service.getProgram(code, options.filePath);
63
62
  }
64
63
  var TSService = class {
64
+ watch;
65
+ patchedHostSet = /* @__PURE__ */ new WeakSet();
66
+ currTarget = {
67
+ code: "",
68
+ filePath: ""
69
+ };
70
+ fileWatchCallbacks = /* @__PURE__ */ new Map();
71
+ ts;
65
72
  constructor(tsconfigPath, ts) {
66
- this.patchedHostSet = /* @__PURE__ */ new WeakSet();
67
- this.currTarget = {
68
- code: "",
69
- filePath: ""
70
- };
71
- this.fileWatchCallbacks = /* @__PURE__ */ new Map();
72
73
  this.ts = ts;
73
74
  this.watch = this.createWatch(tsconfigPath);
74
75
  }
@@ -394,15 +395,15 @@ function getKeys(node, visitorKeys) {
394
395
  function* getNodes(node, key) {
395
396
  const child = node[key];
396
397
  if (Array.isArray(child)) {
397
- for (const c of child) if (isNode(c)) yield c;
398
- } else if (isNode(child)) yield child;
398
+ for (const c of child) if (isNode$1(c)) yield c;
399
+ } else if (isNode$1(child)) yield child;
399
400
  }
400
401
  /**
401
402
  * Check whether a given value is a node.
402
403
  * @param x The value to check.
403
404
  * @returns `true` if the value is a node.
404
405
  */
405
- function isNode(x) {
406
+ function isNode$1(x) {
406
407
  return x !== null && typeof x === "object" && typeof x.type === "string";
407
408
  }
408
409
  /**
@@ -426,248 +427,6 @@ function traverseNodes(node, visitor) {
426
427
  traverse(node, null, visitor);
427
428
  }
428
429
  //#endregion
429
- //#region src/util/index.ts
430
- /**
431
- * Uses a binary search to determine the highest index at which value should be inserted into array in order to maintain its sort order.
432
- */
433
- function sortedLastIndex(array, compare) {
434
- let lower = 0;
435
- let upper = array.length;
436
- while (lower < upper) {
437
- const mid = Math.floor(lower + (upper - lower) / 2);
438
- const target = compare(array[mid]);
439
- if (target < 0) lower = mid + 1;
440
- else if (target > 0) upper = mid;
441
- else return mid + 1;
442
- }
443
- return upper;
444
- }
445
- /**
446
- * Add element to a sorted array
447
- */
448
- function addElementToSortedArray(array, element, compare) {
449
- const index = sortedLastIndex(array, (target) => compare(target, element));
450
- array.splice(index, 0, element);
451
- }
452
- /**
453
- * Add element to a sorted array
454
- */
455
- function addElementsToSortedArray(array, elements, compare) {
456
- if (!elements.length) return;
457
- let last = elements[0];
458
- let index = sortedLastIndex(array, (target) => compare(target, last));
459
- for (const element of elements) {
460
- if (compare(last, element) > 0) index = sortedLastIndex(array, (target) => compare(target, element));
461
- let e = array[index];
462
- while (e && compare(e, element) <= 0) e = array[++index];
463
- array.splice(index, 0, element);
464
- last = element;
465
- }
466
- }
467
- const WRITE_FLAG = 2;
468
- const READ_WRITE_FLAG = 3;
469
- const REFERENCE_TYPE_TYPE_FLAG = 2;
470
- /**
471
- * Gets the scope for the Program node
472
- */
473
- function getProgramScope(scopeManager) {
474
- const globalScope = scopeManager.globalScope;
475
- return globalScope.childScopes.find((s) => s.type === "module") || globalScope;
476
- }
477
- /** Remove all scope, variable, and reference */
478
- function removeAllScopeAndVariableAndReference(target, info) {
479
- const removeTargetScopes = /* @__PURE__ */ new Set();
480
- traverseNodes(target, {
481
- visitorKeys: info.visitorKeys,
482
- enterNode(node) {
483
- const scope = info.scopeManager.acquire(node);
484
- if (scope) {
485
- removeTargetScopes.add(scope);
486
- return;
487
- }
488
- if (node.type === "Identifier" || node.type === "JSXIdentifier") {
489
- let targetScope = getInnermostScopeFromNode(info.scopeManager, node);
490
- while (targetScope && targetScope.block.type !== "Program" && target.range[0] <= targetScope.block.range[0] && targetScope.block.range[1] <= target.range[1]) targetScope = targetScope.upper;
491
- if (removeTargetScopes.has(targetScope)) return;
492
- removeIdentifierVariable(node, targetScope);
493
- removeIdentifierReference(node, targetScope);
494
- }
495
- },
496
- leaveNode() {}
497
- });
498
- for (const scope of removeTargetScopes) removeScope(info.scopeManager, scope);
499
- }
500
- /**
501
- * Add the virtual reference.
502
- */
503
- function addVirtualReference(node, variable, scope, status) {
504
- const reference = new Reference(node, scope, status.write && status.read ? READ_WRITE_FLAG : status.write ? WRITE_FLAG : 1, void 0, void 0, void 0, status.typeRef ? REFERENCE_TYPE_TYPE_FLAG : 1);
505
- reference.astroVirtualReference = true;
506
- addReference(variable.references, reference);
507
- reference.resolved = variable;
508
- if (status.forceUsed) variable.eslintUsed = true;
509
- return reference;
510
- }
511
- /**
512
- * Add global variable
513
- */
514
- function addGlobalVariable(reference, scopeManager) {
515
- const globalScope = scopeManager.globalScope;
516
- const name = reference.identifier.name;
517
- let variable = globalScope.set.get(name);
518
- if (!variable) {
519
- variable = new Variable(name, globalScope);
520
- globalScope.variables.push(variable);
521
- globalScope.set.set(name, variable);
522
- }
523
- reference.resolved = variable;
524
- variable.references.push(reference);
525
- return variable;
526
- }
527
- /** Remove reference from through */
528
- function removeReferenceFromThrough(reference, baseScope) {
529
- const variable = reference.resolved;
530
- const name = reference.identifier.name;
531
- let scope = baseScope;
532
- while (scope) {
533
- for (const ref of [...scope.through]) if (reference === ref) scope.through.splice(scope.through.indexOf(ref), 1);
534
- else if (ref.identifier.name === name) {
535
- ref.resolved = variable;
536
- if (!variable.references.includes(ref)) addReference(variable.references, ref);
537
- scope.through.splice(scope.through.indexOf(ref), 1);
538
- }
539
- scope = scope.upper;
540
- }
541
- }
542
- /** Remove scope */
543
- function removeScope(scopeManager, scope) {
544
- for (const childScope of scope.childScopes) removeScope(scopeManager, childScope);
545
- while (scope.references[0]) removeReference(scope.references[0], scope);
546
- const upper = scope.upper;
547
- if (upper) {
548
- const index = upper.childScopes.indexOf(scope);
549
- if (index >= 0) upper.childScopes.splice(index, 1);
550
- }
551
- const index = scopeManager.scopes.indexOf(scope);
552
- if (index >= 0) scopeManager.scopes.splice(index, 1);
553
- }
554
- /** Remove reference */
555
- function removeReference(reference, baseScope) {
556
- if (reference.resolved) if (reference.resolved.defs.some((d) => d.name === reference.identifier)) {
557
- const varIndex = baseScope.variables.indexOf(reference.resolved);
558
- if (varIndex >= 0) baseScope.variables.splice(varIndex, 1);
559
- const name = reference.identifier.name;
560
- if (reference.resolved === baseScope.set.get(name)) baseScope.set.delete(name);
561
- } else {
562
- const refIndex = reference.resolved.references.indexOf(reference);
563
- if (refIndex >= 0) reference.resolved.references.splice(refIndex, 1);
564
- }
565
- let scope = baseScope;
566
- while (scope) {
567
- const refIndex = scope.references.indexOf(reference);
568
- if (refIndex >= 0) scope.references.splice(refIndex, 1);
569
- const throughIndex = scope.through.indexOf(reference);
570
- if (throughIndex >= 0) scope.through.splice(throughIndex, 1);
571
- scope = scope.upper;
572
- }
573
- }
574
- /** Remove variable */
575
- function removeIdentifierVariable(node, scope) {
576
- for (let varIndex = 0; varIndex < scope.variables.length; varIndex++) {
577
- const variable = scope.variables[varIndex];
578
- const defIndex = variable.defs.findIndex((def) => def.name === node);
579
- if (defIndex < 0) continue;
580
- variable.defs.splice(defIndex, 1);
581
- if (variable.defs.length === 0) {
582
- referencesToThrough(variable.references, scope);
583
- variable.references.forEach((r) => {
584
- if (r.init) r.init = false;
585
- r.resolved = null;
586
- });
587
- scope.variables.splice(varIndex, 1);
588
- const name = node.name;
589
- if (variable === scope.set.get(name)) scope.set.delete(name);
590
- } else {
591
- const idIndex = variable.identifiers.indexOf(node);
592
- if (idIndex >= 0) variable.identifiers.splice(idIndex, 1);
593
- }
594
- return;
595
- }
596
- }
597
- /** Remove reference */
598
- function removeIdentifierReference(node, scope) {
599
- const reference = scope.references.find((ref) => ref.identifier === node);
600
- if (reference) {
601
- removeReference(reference, scope);
602
- return true;
603
- }
604
- const location = node.range[0];
605
- const pendingScopes = [];
606
- for (const childScope of scope.childScopes) {
607
- const range = childScope.block.range;
608
- if (range[0] <= location && location < range[1]) {
609
- if (removeIdentifierReference(node, childScope)) return true;
610
- } else pendingScopes.push(childScope);
611
- }
612
- for (const childScope of pendingScopes) if (removeIdentifierReference(node, childScope)) return true;
613
- return false;
614
- }
615
- /**
616
- * Get the innermost scope which contains a given node.
617
- * @returns The innermost scope.
618
- */
619
- function getInnermostScopeFromNode(scopeManager, currentNode) {
620
- return getInnermostScope(getScopeFromNode(scopeManager, currentNode), currentNode);
621
- }
622
- /**
623
- * Gets the scope for the current node
624
- */
625
- function getScopeFromNode(scopeManager, currentNode) {
626
- let node = currentNode;
627
- for (; node; node = node.parent || null) {
628
- const scope = scopeManager.acquire(node, false);
629
- if (scope) {
630
- if (scope.type === "function-expression-name") return scope.childScopes[0];
631
- if (scope.type === "global" && node.type === "Program" && node.sourceType === "module") return scope.childScopes.find((s) => s.type === "module") || scope;
632
- return scope;
633
- }
634
- }
635
- return scopeManager.globalScope;
636
- }
637
- /**
638
- * Get the innermost scope which contains a given location.
639
- * @param initialScope The initial scope to search.
640
- * @param node The location to search.
641
- * @returns The innermost scope.
642
- */
643
- function getInnermostScope(initialScope, node) {
644
- for (const childScope of initialScope.childScopes) {
645
- const range = childScope.block.range;
646
- if (range[0] <= node.range[0] && node.range[1] <= range[1]) return getInnermostScope(childScope, node);
647
- }
648
- return initialScope;
649
- }
650
- /** Move reference to through */
651
- function referencesToThrough(references, baseScope) {
652
- let scope = baseScope;
653
- while (scope) {
654
- addAllReferences(scope.through, references);
655
- scope = scope.upper;
656
- }
657
- }
658
- /**
659
- * Add all references to array
660
- */
661
- function addAllReferences(list, elements) {
662
- addElementsToSortedArray(list, elements, (a, b) => a.identifier.range[0] - b.identifier.range[0]);
663
- }
664
- /**
665
- * Add reference to array
666
- */
667
- function addReference(list, reference) {
668
- addElementToSortedArray(list, reference, (a, b) => a.identifier.range[0] - b.identifier.range[0]);
669
- }
670
- //#endregion
671
430
  //#region src/parser/eslint-scope.ts
672
431
  let eslintScopeCache = null;
673
432
  /**
@@ -774,31 +533,6 @@ ${code}`);
774
533
  patchResult?.terminate();
775
534
  }
776
535
  }
777
- var Referencer = class extends eslintScope.Referencer {
778
- JSXAttribute(node) {
779
- this.visit(node.value);
780
- }
781
- JSXClosingElement() {}
782
- JSXFragment(node) {
783
- this.visitChildren(node);
784
- }
785
- JSXIdentifier(node) {
786
- const scope = this.currentScope();
787
- const ref = new Reference(node, scope, 1, void 0, void 0, false, 1);
788
- scope.references.push(ref);
789
- scope.__left.push(ref);
790
- }
791
- JSXMemberExpression(node) {
792
- if (node.object.type !== AST_NODE_TYPES.JSXIdentifier) this.visit(node.object);
793
- else if (node.object.name !== "this") this.visit(node.object);
794
- }
795
- JSXOpeningElement(node) {
796
- if (node.name.type === AST_NODE_TYPES.JSXIdentifier) {
797
- if (node.name.name[0].toUpperCase() === node.name.name[0] || node.name.name === "this") this.visit(node.name);
798
- } else this.visit(node.name);
799
- for (const attr of node.attributes) this.visit(attr);
800
- }
801
- };
802
536
  /**
803
537
  * Analyzed scopes for JavaScript.
804
538
  */
@@ -810,11 +544,10 @@ function analyzeForEcmaScript(tree, providedOptions) {
810
544
  sourceType: "script",
811
545
  ecmaVersion: 5,
812
546
  childVisitorKeys: null,
813
- fallback: "iteration"
547
+ fallback: "iteration",
548
+ jsx: true
814
549
  }, providedOptions);
815
- const scopeManager = new eslintScope.ScopeManager(options);
816
- new Referencer(options, scopeManager).visit(tree);
817
- return scopeManager;
550
+ return eslintScope.analyze(tree, options);
818
551
  }
819
552
  //#endregion
820
553
  //#region src/parser/sort.ts
@@ -828,403 +561,12 @@ function sort(tokens) {
828
561
  });
829
562
  }
830
563
  //#endregion
831
- //#region src/errors.ts
832
- /**
833
- * Astro parse errors.
834
- */
835
- var ParseError = class extends SyntaxError {
836
- /**
837
- * Initialize this ParseError instance.
838
- */
839
- constructor(message, offset, ctx) {
840
- super(message);
841
- if (typeof offset === "number") {
842
- this.index = offset;
843
- const loc = ctx.getLocFromIndex(offset);
844
- this.lineNumber = loc.line;
845
- this.column = loc.column;
846
- } else {
847
- this.index = ctx.getIndexFromLoc(offset);
848
- this.lineNumber = offset.line;
849
- this.column = offset.column;
850
- }
851
- this.originalAST = ctx.originalAST;
852
- }
853
- };
854
- //#endregion
855
- //#region src/astro/index.ts
856
- /**
857
- * Checks if the given node is TagLikeNode
858
- */
859
- function isTag(node) {
860
- return node.type === "element" || node.type === "custom-element" || node.type === "component" || node.type === "fragment";
861
- }
862
- /**
863
- * Checks if the given node is ParentNode
864
- */
865
- function isParent(node) {
866
- return Array.isArray(node.children);
867
- }
868
- /** walk element nodes */
869
- function walkElements(parent, code, enter, leave, parents = []) {
870
- const children = getSortedChildren(parent, code);
871
- const currParents = [parent, ...parents];
872
- for (const node of children) {
873
- enter(node, currParents);
874
- if (isParent(node)) walkElements(node, code, enter, leave, currParents);
875
- leave(node, currParents);
876
- }
877
- }
878
- /** walk nodes */
879
- function walk(parent, code, enter, leave) {
880
- walkElements(parent, code, (node, parents) => {
881
- enter(node, parents);
882
- if (isTag(node)) {
883
- const attrParents = [node, ...parents];
884
- for (const attr of node.attributes) {
885
- enter(attr, attrParents);
886
- leave(attr, attrParents);
887
- }
888
- }
889
- }, leave);
890
- }
891
- /**
892
- * Get end offset of start tag
893
- */
894
- function calcStartTagEndOffset(node, ctx) {
895
- const lastAttr = node.attributes[node.attributes.length - 1];
896
- let beforeCloseIndex;
897
- if (lastAttr) beforeCloseIndex = calcAttributeEndOffset(lastAttr, ctx);
898
- else {
899
- const info = getTokenInfo(ctx, [`<${node.name}`], node.position.start.offset);
900
- beforeCloseIndex = info.index + info.match.length;
901
- }
902
- const info = getTokenInfo(ctx, [[">", "/>"]], beforeCloseIndex);
903
- return info.index + info.match.length;
904
- }
905
- /**
906
- * Get end offset of attribute
907
- */
908
- function calcAttributeEndOffset(node, ctx) {
909
- let info;
910
- if (node.kind === "empty") info = getTokenInfo(ctx, [node.name], node.position.start.offset);
911
- else if (node.kind === "quoted") info = getTokenInfo(ctx, [[
912
- {
913
- token: `"${node.value}"`,
914
- htmlEntityDecode: true
915
- },
916
- {
917
- token: `'${node.value}'`,
918
- htmlEntityDecode: true
919
- },
920
- {
921
- token: node.value,
922
- htmlEntityDecode: true
923
- }
924
- ]], calcAttributeValueStartOffset(node, ctx));
925
- else if (node.kind === "expression") info = getTokenInfo(ctx, [
926
- "{",
927
- node.value,
928
- "}"
929
- ], calcAttributeValueStartOffset(node, ctx));
930
- else if (node.kind === "shorthand") info = getTokenInfo(ctx, [
931
- "{",
932
- node.name,
933
- "}"
934
- ], node.position.start.offset);
935
- else if (node.kind === "spread") info = getTokenInfo(ctx, [
936
- "{",
937
- "...",
938
- node.name,
939
- "}"
940
- ], node.position.start.offset);
941
- else if (node.kind === "template-literal") info = getTokenInfo(ctx, [`\`${node.value}\``], calcAttributeValueStartOffset(node, ctx));
942
- else throw new ParseError(`Unknown attr kind: ${node.kind}`, node.position.start.offset, ctx);
943
- return info.index + info.match.length;
944
- }
945
- /**
946
- * Get start offset of attribute value
947
- */
948
- function calcAttributeValueStartOffset(node, ctx) {
949
- let info;
950
- if (node.kind === "quoted") info = getTokenInfo(ctx, [
951
- node.name,
952
- "=",
953
- [
954
- `"`,
955
- `'`,
956
- {
957
- token: node.value,
958
- htmlEntityDecode: true
959
- }
960
- ]
961
- ], node.position.start.offset);
962
- else if (node.kind === "expression") info = getTokenInfo(ctx, [
963
- node.name,
964
- "=",
965
- "{"
966
- ], node.position.start.offset);
967
- else if (node.kind === "template-literal") info = getTokenInfo(ctx, [
968
- node.name,
969
- "=",
970
- "`"
971
- ], node.position.start.offset);
972
- else throw new ParseError(`Unknown attr kind: ${node.kind}`, node.position.start.offset, ctx);
973
- return info.index;
974
- }
975
- /**
976
- * Get end offset of tag
977
- */
978
- function getEndOffset(node, ctx) {
979
- if (node.position.end?.offset != null) return node.position.end.offset;
980
- if (isTag(node)) return calcTagEndOffset(node, ctx);
981
- if (node.type === "expression") return calcExpressionEndOffset(node, ctx);
982
- if (node.type === "comment") return calcCommentEndOffset(node, ctx);
983
- if (node.type === "frontmatter") {
984
- const start = node.position.start.offset;
985
- return ctx.code.indexOf("---", start + 3) + 3;
986
- }
987
- if (node.type === "doctype") {
988
- const start = node.position.start.offset;
989
- return ctx.code.indexOf(">", start) + 1;
990
- }
991
- if (node.type === "text") return node.position.start.offset + node.value.length;
992
- if (node.type === "root") return ctx.code.length;
993
- throw new Error(`unknown type: ${node.type}`);
994
- }
995
- /**
996
- * Get content end offset
997
- */
998
- function calcContentEndOffset(parent, ctx) {
999
- const code = ctx.code;
1000
- if (isTag(parent)) {
1001
- const end = getEndOffset(parent, ctx);
1002
- if (code[end - 1] !== ">") return end;
1003
- const index = code.lastIndexOf("</", end - 1);
1004
- if (index >= 0 && code.slice(index + 2, end - 1).trim() === parent.name) return index;
1005
- return end;
1006
- } else if (parent.type === "expression") {
1007
- const end = getEndOffset(parent, ctx);
1008
- return code.lastIndexOf("}", end);
1009
- } else if (parent.type === "root") return code.length;
1010
- throw new Error(`unknown type: ${parent.type}`);
1011
- }
1012
- /**
1013
- * If the given tag is a self-close tag, get the self-closing tag.
1014
- */
1015
- function getSelfClosingTag(node, ctx) {
1016
- if (node.children.length > 0) return null;
1017
- const code = ctx.code;
1018
- const startTagEndOffset = calcStartTagEndOffset(node, ctx);
1019
- if (code.startsWith("/>", startTagEndOffset - 2)) return {
1020
- offset: startTagEndOffset,
1021
- end: "/>"
1022
- };
1023
- if (code.startsWith(`</${node.name}`, startTagEndOffset)) return null;
1024
- return {
1025
- offset: startTagEndOffset,
1026
- end: ">"
1027
- };
1028
- }
1029
- /**
1030
- * If the given tag has a end tag, get the end tag.
1031
- */
1032
- function getEndTag(node, ctx) {
1033
- let beforeIndex;
1034
- if (node.children.length) {
1035
- const lastChild = node.children[node.children.length - 1];
1036
- beforeIndex = getEndOffset(lastChild, ctx);
1037
- } else beforeIndex = calcStartTagEndOffset(node, ctx);
1038
- beforeIndex = skipSpaces(ctx.code, beforeIndex);
1039
- if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
1040
- const offset = beforeIndex;
1041
- beforeIndex = beforeIndex + 2 + node.name.length;
1042
- const info = getTokenInfo(ctx, [">"], beforeIndex);
1043
- const end = info.index + info.match.length;
1044
- return {
1045
- offset,
1046
- tag: ctx.code.slice(offset, end)
1047
- };
1048
- }
1049
- return null;
1050
- }
1051
- /**
1052
- * Get end offset of comment
1053
- */
1054
- function calcCommentEndOffset(node, ctx) {
1055
- const info = getTokenInfo(ctx, [
1056
- "<!--",
1057
- node.value,
1058
- "-->"
1059
- ], node.position.start.offset);
1060
- return info.index + info.match.length;
1061
- }
1062
- /**
1063
- * Get end offset of tag
1064
- */
1065
- function calcTagEndOffset(node, ctx) {
1066
- let beforeIndex;
1067
- if (node.children.length) {
1068
- const lastChild = node.children[node.children.length - 1];
1069
- beforeIndex = getEndOffset(lastChild, ctx);
1070
- } else beforeIndex = calcStartTagEndOffset(node, ctx);
1071
- beforeIndex = skipSpaces(ctx.code, beforeIndex);
1072
- if (ctx.code.startsWith(`</${node.name}`, beforeIndex)) {
1073
- beforeIndex = beforeIndex + 2 + node.name.length;
1074
- const info = getTokenInfo(ctx, [">"], beforeIndex);
1075
- return info.index + info.match.length;
1076
- }
1077
- return beforeIndex;
1078
- }
1079
- /**
1080
- * Get end offset of Expression
1081
- */
1082
- function calcExpressionEndOffset(node, ctx) {
1083
- if (node.children.length) {
1084
- const lastChild = node.children[node.children.length - 1];
1085
- const info = getTokenInfo(ctx, ["}"], getEndOffset(lastChild, ctx));
1086
- return info.index + info.match.length;
1087
- }
1088
- const info = getTokenInfo(ctx, ["{", "}"], node.position.start.offset);
1089
- return info.index + info.match.length;
1090
- }
1091
- /**
1092
- * Get token info
1093
- */
1094
- function getTokenInfo(ctx, tokens, position) {
1095
- let lastMatch;
1096
- for (const t of tokens) {
1097
- const index = lastMatch ? lastMatch.index + lastMatch.match.length : position;
1098
- const m = Array.isArray(t) ? matchOfForMulti(t, index) : match(t, index);
1099
- if (m == null) throw new ParseError(`Unknown token at ${index}, expected: ${JSON.stringify(t)}, actual: ${JSON.stringify(ctx.code.slice(index, index + 10))}`, index, ctx);
1100
- lastMatch = m;
1101
- }
1102
- return lastMatch;
1103
- /**
1104
- * For Single Token
1105
- */
1106
- function match(token, position) {
1107
- const search = typeof token === "string" ? token : token.token;
1108
- const index = search.trim() === search ? skipSpaces(ctx.code, position) : position;
1109
- if (ctx.code.startsWith(search, index)) return {
1110
- match: search,
1111
- index
1112
- };
1113
- if (typeof token !== "string") return matchWithHTMLEntity(token, index);
1114
- return null;
1115
- }
1116
- /**
1117
- * For Multiple Token
1118
- */
1119
- function matchOfForMulti(search, position) {
1120
- for (const s of search) {
1121
- const m = match(s, position);
1122
- if (m) return m;
1123
- }
1124
- return null;
1125
- }
1126
- /**
1127
- * With HTML entity
1128
- */
1129
- function matchWithHTMLEntity(token, position) {
1130
- const search = token.token;
1131
- let codeOffset = position;
1132
- let searchOffset = 0;
1133
- while (searchOffset < search.length) {
1134
- const searchChar = search[searchOffset];
1135
- if (ctx.code[codeOffset] === searchChar) {
1136
- if (searchChar === "&") {
1137
- const entityCandidate = ctx.code.slice(codeOffset, codeOffset + 5);
1138
- if (entityCandidate === "&amp;" || entityCandidate === "&AMP;") {
1139
- codeOffset += 5;
1140
- searchOffset++;
1141
- continue;
1142
- }
1143
- }
1144
- codeOffset++;
1145
- searchOffset++;
1146
- continue;
1147
- }
1148
- const entity = getHTMLEntity(codeOffset);
1149
- if (entity?.entity === searchChar) {
1150
- codeOffset += entity.length;
1151
- searchOffset++;
1152
- continue;
1153
- }
1154
- return null;
1155
- }
1156
- return {
1157
- match: ctx.code.slice(position, codeOffset),
1158
- index: position
1159
- };
1160
- /**
1161
- * Get HTML entity from the given position
1162
- */
1163
- function getHTMLEntity(position) {
1164
- let codeOffset = position;
1165
- if (ctx.code[codeOffset++] !== "&") return null;
1166
- let entity = "";
1167
- const entityDecoder = new EntityDecoder(htmlDecodeTree, (cp) => entity += String.fromCodePoint(cp));
1168
- entityDecoder.startEntity(DecodingMode.Attribute);
1169
- const length = entityDecoder.write(ctx.code, codeOffset);
1170
- if (length < 0) return null;
1171
- if (length === 0) return null;
1172
- return {
1173
- entity,
1174
- length
1175
- };
1176
- }
1177
- }
1178
- }
1179
- /**
1180
- * Skip spaces
1181
- */
1182
- function skipSpaces(string, position) {
1183
- const re = /\s*/g;
1184
- re.lastIndex = position;
1185
- const match = re.exec(string);
1186
- if (match) return match.index + match[0].length;
1187
- return position;
1188
- }
1189
- /**
1190
- * Get children
1191
- */
1192
- function getSortedChildren(parent, code) {
1193
- if (parent.type === "root" && parent.children[0]?.type === "frontmatter") {
1194
- const children = [...parent.children];
1195
- if (children.every((n) => n.position)) return children.sort((a, b) => a.position.start.offset - b.position.start.offset);
1196
- let start = skipSpaces(code, 0);
1197
- if (code.startsWith("<!", start)) {
1198
- const frontmatter = children.shift();
1199
- const before = [];
1200
- let first;
1201
- while (first = children.shift()) {
1202
- start = skipSpaces(code, start);
1203
- if (first.type === "comment" && code.startsWith("<!--", start)) {
1204
- start = code.indexOf("-->", start + 4) + 3;
1205
- before.push(first);
1206
- } else if (first.type === "doctype" && code.startsWith("<!", start)) {
1207
- start = code.indexOf(">", start + 2) + 1;
1208
- before.push(first);
1209
- } else {
1210
- children.unshift(first);
1211
- break;
1212
- }
1213
- }
1214
- return [
1215
- ...before,
1216
- frontmatter,
1217
- ...children
1218
- ];
1219
- }
1220
- }
1221
- return parent.children;
1222
- }
1223
- //#endregion
1224
564
  //#region src/context/restore.ts
1225
565
  var RestoreNodeProcessContext = class {
566
+ result;
567
+ removeTokens = /* @__PURE__ */ new Set();
568
+ nodeMap;
1226
569
  constructor(result, nodeMap) {
1227
- this.removeTokens = /* @__PURE__ */ new Set();
1228
570
  this.result = result;
1229
571
  this.nodeMap = nodeMap;
1230
572
  }
@@ -1239,11 +581,12 @@ var RestoreNodeProcessContext = class {
1239
581
  }
1240
582
  };
1241
583
  var RestoreContext = class {
584
+ ctx;
585
+ offsets = [];
586
+ virtualFragments = [];
587
+ restoreNodeProcesses = [];
588
+ tokens = [];
1242
589
  constructor(ctx) {
1243
- this.offsets = [];
1244
- this.virtualFragments = [];
1245
- this.restoreNodeProcesses = [];
1246
- this.tokens = [];
1247
590
  this.ctx = ctx;
1248
591
  }
1249
592
  addRestoreNodeProcess(process) {
@@ -1369,9 +712,11 @@ function restoreNodes(result, nodeMap, restoreNodeProcesses) {
1369
712
  //#endregion
1370
713
  //#region src/context/script.ts
1371
714
  var VirtualScriptContext = class {
715
+ originalCode;
716
+ restoreContext;
717
+ script = "";
718
+ consumedIndex = 0;
1372
719
  constructor(ctx) {
1373
- this.script = "";
1374
- this.consumedIndex = 0;
1375
720
  this.originalCode = ctx.code;
1376
721
  this.restoreContext = new RestoreContext(ctx);
1377
722
  }
@@ -1381,21 +726,393 @@ var VirtualScriptContext = class {
1381
726
  skipUntilOriginalOffset(offset) {
1382
727
  this.consumedIndex = Math.max(offset, this.consumedIndex);
1383
728
  }
1384
- appendOriginal(index) {
1385
- if (this.consumedIndex >= index) return;
1386
- this.restoreContext.addOffset({
1387
- original: this.consumedIndex,
1388
- dist: this.script.length
1389
- });
1390
- this.script += this.originalCode.slice(this.consumedIndex, index);
1391
- this.consumedIndex = index;
729
+ appendOriginal(index) {
730
+ if (this.consumedIndex >= index) return;
731
+ this.restoreContext.addOffset({
732
+ original: this.consumedIndex,
733
+ dist: this.script.length
734
+ });
735
+ this.script += this.originalCode.slice(this.consumedIndex, index);
736
+ this.consumedIndex = index;
737
+ }
738
+ appendVirtualScript(virtualFragment) {
739
+ const start = this.script.length;
740
+ this.script += virtualFragment;
741
+ this.restoreContext.addVirtualFragmentRange(start, this.script.length);
742
+ }
743
+ };
744
+ //#endregion
745
+ //#region src/util/index.ts
746
+ /**
747
+ * Uses a binary search to determine the highest index at which value should be inserted into array in order to maintain its sort order.
748
+ */
749
+ function sortedLastIndex(array, compare) {
750
+ let lower = 0;
751
+ let upper = array.length;
752
+ while (lower < upper) {
753
+ const mid = Math.floor(lower + (upper - lower) / 2);
754
+ const target = compare(array[mid]);
755
+ if (target < 0) lower = mid + 1;
756
+ else if (target > 0) upper = mid;
757
+ else return mid + 1;
758
+ }
759
+ return upper;
760
+ }
761
+ /**
762
+ * Add element to a sorted array
763
+ */
764
+ function addElementToSortedArray(array, element, compare) {
765
+ const index = sortedLastIndex(array, (target) => compare(target, element));
766
+ array.splice(index, 0, element);
767
+ }
768
+ /**
769
+ * Add element to a sorted array
770
+ */
771
+ function addElementsToSortedArray(array, elements, compare) {
772
+ if (!elements.length) return;
773
+ let last = elements[0];
774
+ let index = sortedLastIndex(array, (target) => compare(target, last));
775
+ for (const element of elements) {
776
+ if (compare(last, element) > 0) index = sortedLastIndex(array, (target) => compare(target, element));
777
+ let e = array[index];
778
+ while (e && compare(e, element) <= 0) e = array[++index];
779
+ array.splice(index, 0, element);
780
+ last = element;
781
+ }
782
+ }
783
+ //#endregion
784
+ //#region src/parser/scope/index.ts
785
+ const REFERENCE_TYPE_VALUE_FLAG = 1;
786
+ const REFERENCE_TYPE_TYPE_FLAG = 2;
787
+ /**
788
+ * Gets the scope for the Program node
789
+ */
790
+ function getProgramScope(scopeManager) {
791
+ const globalScope = scopeManager.globalScope;
792
+ return globalScope.childScopes.find((s) => s.type === "module") || globalScope;
793
+ }
794
+ /** Remove all scope, variable, and reference */
795
+ function removeAllScopeAndVariableAndReference(target, info) {
796
+ const removeTargetScopes = /* @__PURE__ */ new Set();
797
+ traverseNodes(target, {
798
+ visitorKeys: info.visitorKeys,
799
+ enterNode(node) {
800
+ const scope = info.scopeManager.acquire(node);
801
+ if (scope) {
802
+ removeTargetScopes.add(scope);
803
+ return;
804
+ }
805
+ if (node.type === "Identifier" || node.type === "JSXIdentifier") {
806
+ let targetScope = getInnermostScopeFromNode(info.scopeManager, node);
807
+ while (targetScope && targetScope.block.type !== "Program" && target.range[0] <= targetScope.block.range[0] && targetScope.block.range[1] <= target.range[1]) targetScope = targetScope.upper;
808
+ if (removeTargetScopes.has(targetScope)) return;
809
+ removeIdentifierVariable(node, targetScope);
810
+ removeIdentifierReference(node, targetScope);
811
+ }
812
+ },
813
+ leaveNode() {}
814
+ });
815
+ for (const scope of removeTargetScopes) removeScope(info.scopeManager, scope);
816
+ }
817
+ /**
818
+ * Add the virtual reference.
819
+ */
820
+ function addVirtualReference(node, variable, scope, status) {
821
+ const reference = new Reference(node, scope, status.write && status.read ? eslintScope$1.Reference.RW : status.write ? eslintScope$1.Reference.WRITE : eslintScope$1.Reference.READ, void 0, void 0, void 0, status.typeRef ? REFERENCE_TYPE_TYPE_FLAG : REFERENCE_TYPE_VALUE_FLAG);
822
+ reference.astroVirtualReference = true;
823
+ addReference(variable.references, reference);
824
+ reference.resolved = variable;
825
+ if (status.forceUsed) variable.eslintUsed = true;
826
+ return reference;
827
+ }
828
+ /**
829
+ * Add global variable
830
+ */
831
+ function addGlobalVariable(reference, scopeManager) {
832
+ const globalScope = scopeManager.globalScope;
833
+ const name = reference.identifier.name;
834
+ let variable = globalScope.set.get(name);
835
+ if (!variable) {
836
+ variable = new Variable(name, globalScope);
837
+ globalScope.variables.push(variable);
838
+ globalScope.set.set(name, variable);
839
+ }
840
+ reference.resolved = variable;
841
+ variable.references.push(reference);
842
+ return variable;
843
+ }
844
+ /** Remove reference from through */
845
+ function removeReferenceFromThrough(reference, baseScope) {
846
+ const variable = reference.resolved;
847
+ const name = reference.identifier.name;
848
+ let scope = baseScope;
849
+ while (scope) {
850
+ for (const ref of [...scope.through]) if (reference === ref) scope.through.splice(scope.through.indexOf(ref), 1);
851
+ else if (ref.identifier.name === name) {
852
+ ref.resolved = variable;
853
+ if (!variable.references.includes(ref)) addReference(variable.references, ref);
854
+ scope.through.splice(scope.through.indexOf(ref), 1);
855
+ }
856
+ scope = scope.upper;
857
+ }
858
+ }
859
+ /** Remove scope */
860
+ function removeScope(scopeManager, scope) {
861
+ for (const childScope of scope.childScopes) removeScope(scopeManager, childScope);
862
+ while (scope.references[0]) removeReference(scope.references[0], scope);
863
+ const upper = scope.upper;
864
+ if (upper) {
865
+ const index = upper.childScopes.indexOf(scope);
866
+ if (index >= 0) upper.childScopes.splice(index, 1);
867
+ }
868
+ const index = scopeManager.scopes.indexOf(scope);
869
+ if (index >= 0) scopeManager.scopes.splice(index, 1);
870
+ }
871
+ /** Remove reference */
872
+ function removeReference(reference, baseScope) {
873
+ if (reference.resolved) if (reference.resolved.defs.some((d) => d.name === reference.identifier)) {
874
+ const varIndex = baseScope.variables.indexOf(reference.resolved);
875
+ if (varIndex >= 0) baseScope.variables.splice(varIndex, 1);
876
+ const name = reference.identifier.name;
877
+ if (reference.resolved === baseScope.set.get(name)) baseScope.set.delete(name);
878
+ } else {
879
+ const refIndex = reference.resolved.references.indexOf(reference);
880
+ if (refIndex >= 0) reference.resolved.references.splice(refIndex, 1);
881
+ }
882
+ let scope = baseScope;
883
+ while (scope) {
884
+ const refIndex = scope.references.indexOf(reference);
885
+ if (refIndex >= 0) scope.references.splice(refIndex, 1);
886
+ const throughIndex = scope.through.indexOf(reference);
887
+ if (throughIndex >= 0) scope.through.splice(throughIndex, 1);
888
+ scope = scope.upper;
889
+ }
890
+ }
891
+ /** Remove variable */
892
+ function removeIdentifierVariable(node, scope) {
893
+ for (let varIndex = 0; varIndex < scope.variables.length; varIndex++) {
894
+ const variable = scope.variables[varIndex];
895
+ const defIndex = variable.defs.findIndex((def) => def.name === node);
896
+ if (defIndex < 0) continue;
897
+ variable.defs.splice(defIndex, 1);
898
+ if (variable.defs.length === 0) {
899
+ referencesToThrough(variable.references, scope);
900
+ variable.references.forEach((r) => {
901
+ if (r.init) r.init = false;
902
+ r.resolved = null;
903
+ });
904
+ scope.variables.splice(varIndex, 1);
905
+ const name = node.name;
906
+ if (variable === scope.set.get(name)) scope.set.delete(name);
907
+ } else {
908
+ const idIndex = variable.identifiers.indexOf(node);
909
+ if (idIndex >= 0) variable.identifiers.splice(idIndex, 1);
910
+ }
911
+ return;
912
+ }
913
+ }
914
+ /** Remove reference */
915
+ function removeIdentifierReference(node, scope) {
916
+ const reference = scope.references.find((ref) => ref.identifier === node);
917
+ if (reference) {
918
+ removeReference(reference, scope);
919
+ return true;
920
+ }
921
+ const location = node.range[0];
922
+ const pendingScopes = [];
923
+ for (const childScope of scope.childScopes) {
924
+ const range = childScope.block.range;
925
+ if (range[0] <= location && location < range[1]) {
926
+ if (removeIdentifierReference(node, childScope)) return true;
927
+ } else pendingScopes.push(childScope);
928
+ }
929
+ for (const childScope of pendingScopes) if (removeIdentifierReference(node, childScope)) return true;
930
+ return false;
931
+ }
932
+ /**
933
+ * Get the innermost scope which contains a given node.
934
+ * @returns The innermost scope.
935
+ */
936
+ function getInnermostScopeFromNode(scopeManager, currentNode) {
937
+ return getInnermostScope(getScopeFromNode(scopeManager, currentNode), currentNode);
938
+ }
939
+ /**
940
+ * Gets the scope for the current node
941
+ */
942
+ function getScopeFromNode(scopeManager, currentNode) {
943
+ let node = currentNode;
944
+ for (; node; node = node.parent || null) {
945
+ const scope = scopeManager.acquire(node, false);
946
+ if (scope) {
947
+ if (scope.type === "function-expression-name") return scope.childScopes[0];
948
+ if (scope.type === "global" && node.type === "Program" && node.sourceType === "module") return scope.childScopes.find((s) => s.type === "module") || scope;
949
+ return scope;
950
+ }
951
+ }
952
+ return scopeManager.globalScope;
953
+ }
954
+ /**
955
+ * Get the innermost scope which contains a given location.
956
+ * @param initialScope The initial scope to search.
957
+ * @param node The location to search.
958
+ * @returns The innermost scope.
959
+ */
960
+ function getInnermostScope(initialScope, node) {
961
+ for (const childScope of initialScope.childScopes) {
962
+ const range = childScope.block.range;
963
+ if (range[0] <= node.range[0] && node.range[1] <= range[1]) return getInnermostScope(childScope, node);
1392
964
  }
1393
- appendVirtualScript(virtualFragment) {
1394
- const start = this.script.length;
1395
- this.script += virtualFragment;
1396
- this.restoreContext.addVirtualFragmentRange(start, this.script.length);
965
+ return initialScope;
966
+ }
967
+ /** Move reference to through */
968
+ function referencesToThrough(references, baseScope) {
969
+ let scope = baseScope;
970
+ while (scope) {
971
+ addAllReferences(scope.through, references);
972
+ scope = scope.upper;
1397
973
  }
1398
- };
974
+ }
975
+ /**
976
+ * Add all references to array
977
+ */
978
+ function addAllReferences(list, elements) {
979
+ addElementsToSortedArray(list, elements, (a, b) => a.identifier.range[0] - b.identifier.range[0]);
980
+ }
981
+ /**
982
+ * Add reference to array
983
+ */
984
+ function addReference(list, reference) {
985
+ addElementToSortedArray(list, reference, (a, b) => a.identifier.range[0] - b.identifier.range[0]);
986
+ }
987
+ //#endregion
988
+ //#region src/astro/node.ts
989
+ /** Check whether the given node is a JSX element or fragment. */
990
+ function isJSXElementOrFragment(node) {
991
+ return node.type === "JSXElement" || node.type === "JSXFragment";
992
+ }
993
+ /**
994
+ * Check whether a JSX fragment is only a compiler wrapper.
995
+ *
996
+ * e.g `<div></div><div></div>`
997
+ *
998
+ * In this case, the compiler wraps the two divs in a fragment,
999
+ * but the fragment itself has no location in the source code.
1000
+ * We can identify such fragments by checking if their opening and closing tags have zero length.
1001
+ */
1002
+ function isSyntheticFragment(node) {
1003
+ return node.openingFragment.start === node.openingFragment.end && (node.closingFragment == null || node.closingFragment.start === node.closingFragment.end);
1004
+ }
1005
+ /** Check whether an attribute uses Astro shorthand syntax. */
1006
+ function isShorthandAttribute(attr, code) {
1007
+ if (attr.type !== "JSXAttribute" || attr.name.type !== "JSXIdentifier" || attr.value?.type !== "JSXExpressionContainer") return false;
1008
+ return (isIdentifier(attr.value.expression) ? attr.value.expression.name : code.slice(attr.value.expression.start, attr.value.expression.end)) === attr.name.name && attr.value.expression.start === attr.name.start && attr.value.expression.end === attr.name.end;
1009
+ }
1010
+ /** Check whether an unknown value has the node location shape. */
1011
+ function isNode(node) {
1012
+ return Boolean(node) && typeof node === "object" && typeof node.type === "string" && typeof node.start === "number" && typeof node.end === "number";
1013
+ }
1014
+ /** Check whether the given node is an identifier. */
1015
+ function isIdentifier(node) {
1016
+ return isNode(node) && node.type === "Identifier";
1017
+ }
1018
+ //#endregion
1019
+ //#region src/astro/walker.ts
1020
+ /**
1021
+ * Walk the compiler AST.
1022
+ * The `enter` callback is called when entering a node, and the `leave` callback is called when leaving a node.
1023
+ * The `ctx` object passed to the callbacks has two methods: `skipChildren()` to skip walking the children of the current node, and `break()` to stop walking entirely.
1024
+ * The callbacks are called in depth-first order.
1025
+ */
1026
+ function walk(parent, enter, leave) {
1027
+ const childNodes = getChildNodes(parent);
1028
+ const parents = [parent];
1029
+ for (const child of childNodes) if (walkNode(child, enter, leave || (() => {}), parents).break) break;
1030
+ }
1031
+ /** Walk one compiler child node. */
1032
+ function walkNode(node, enter, leave, parents) {
1033
+ const buffer = [{
1034
+ node,
1035
+ parents
1036
+ }];
1037
+ let shouldBreak = false;
1038
+ let shouldSkipChildren = false;
1039
+ const currentCtx = {
1040
+ skipChildren() {
1041
+ shouldSkipChildren = true;
1042
+ },
1043
+ break() {
1044
+ shouldBreak = true;
1045
+ }
1046
+ };
1047
+ while (buffer.length > 0) {
1048
+ const current = buffer.pop();
1049
+ enter(current.node, current.parents, currentCtx);
1050
+ if (shouldBreak) return { break: true };
1051
+ if (shouldSkipChildren) shouldSkipChildren = false;
1052
+ else {
1053
+ const childNodes = getChildNodes(current.node);
1054
+ const parents = [current.node, ...current.parents];
1055
+ for (let i = childNodes.length - 1; i >= 0; i--) buffer.push({
1056
+ node: childNodes[i],
1057
+ parents
1058
+ });
1059
+ }
1060
+ leave(current.node, current.parents, currentCtx);
1061
+ if (shouldBreak) return { break: true };
1062
+ }
1063
+ return {};
1064
+ }
1065
+ /**
1066
+ * Get child nodes of a compiler node.
1067
+ */
1068
+ function getChildNodes(node) {
1069
+ if (isAstroRoot(node)) return [...node.frontmatter ? [node.frontmatter] : [], ...node.body].sort((a, b) => a.start - b.start);
1070
+ if (isAstroFrontmatter(node)) return [node.program];
1071
+ if (isAstroComment(node) || isAstroDoctype(node)) return [];
1072
+ if (isJSXFragment(node)) return [
1073
+ node.openingFragment,
1074
+ ...node.children,
1075
+ ...node.closingFragment ? [node.closingFragment] : []
1076
+ ];
1077
+ if (isJSXElement(node)) return [
1078
+ node.openingElement,
1079
+ ...node.children,
1080
+ ...node.closingElement ? [node.closingElement] : []
1081
+ ];
1082
+ const keys = getKeys(node);
1083
+ const children = [];
1084
+ for (const key of keys) {
1085
+ const value = node[key];
1086
+ if (Array.isArray(value)) {
1087
+ for (const element of value) if (isNode(element)) children.push(element);
1088
+ } else if (isNode(value)) children.push(value);
1089
+ }
1090
+ return children.sort((a, b) => a.start - b.start);
1091
+ }
1092
+ /** Check whether the given node is a JSX element */
1093
+ function isJSXElement(node) {
1094
+ return node.type === "JSXElement";
1095
+ }
1096
+ /** Check whether the given node is a JSX fragment */
1097
+ function isJSXFragment(node) {
1098
+ return node.type === "JSXFragment";
1099
+ }
1100
+ /** Check whether the given node is the root node of the Astro AST. */
1101
+ function isAstroRoot(node) {
1102
+ return node.type === "AstroRoot";
1103
+ }
1104
+ /** Check whether the given node is a walkable template node. */
1105
+ function isAstroFrontmatter(node) {
1106
+ return node.type === "AstroFrontmatter";
1107
+ }
1108
+ /** Check whether the given node is a walkable template node. */
1109
+ function isAstroComment(node) {
1110
+ return node.type === "AstroComment";
1111
+ }
1112
+ /** Check whether the given node is a walkable template node. */
1113
+ function isAstroDoctype(node) {
1114
+ return node.type === "AstroDoctype";
1115
+ }
1399
1116
  //#endregion
1400
1117
  //#region src/parser/process-template.ts
1401
1118
  /**
@@ -1405,6 +1122,7 @@ function processTemplate(ctx, resultTemplate) {
1405
1122
  let uniqueIdSeq = 0;
1406
1123
  const usedUniqueIds = /* @__PURE__ */ new Set();
1407
1124
  const script = new VirtualScriptContext(ctx);
1125
+ const code = ctx.code;
1408
1126
  let fragmentOpened = false;
1409
1127
  /** Open astro root fragment */
1410
1128
  function openRootFragment(startOffset) {
@@ -1422,20 +1140,21 @@ function processTemplate(ctx, resultTemplate) {
1422
1140
  return false;
1423
1141
  });
1424
1142
  }
1425
- walkElements(resultTemplate.ast, ctx.code, (node, [parent]) => {
1426
- if (node.type === "frontmatter") {
1427
- const start = node.position.start.offset;
1143
+ walkElements(resultTemplate.ast, (node) => {
1144
+ if (node.type === "AstroFrontmatter") {
1428
1145
  if (fragmentOpened) {
1429
1146
  script.appendVirtualScript("</>;");
1430
1147
  fragmentOpened = false;
1431
1148
  }
1149
+ let start = node.start;
1150
+ while (code[start] !== "-") start++;
1432
1151
  script.appendOriginal(start);
1433
1152
  script.skipOriginalOffset(3);
1434
- const end = getEndOffset(node, ctx);
1153
+ const end = node.end;
1435
1154
  const scriptStart = start + 3;
1436
1155
  let scriptEnd = end - 3;
1437
1156
  let endChar;
1438
- while (scriptStart < scriptEnd - 1 && (endChar = ctx.code[scriptEnd - 1]) && !endChar.trim()) scriptEnd--;
1157
+ while (scriptStart < scriptEnd - 1 && (endChar = code[scriptEnd - 1]) && !endChar.trim()) scriptEnd--;
1439
1158
  script.appendOriginal(scriptEnd);
1440
1159
  script.appendVirtualScript("\n;");
1441
1160
  script.skipOriginalOffset(end - scriptEnd);
@@ -1451,132 +1170,136 @@ function processTemplate(ctx, resultTemplate) {
1451
1170
  }
1452
1171
  return true;
1453
1172
  });
1454
- script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [node.position.start.offset, node.position.start.offset + 3]);
1173
+ script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [start, start + 3]);
1455
1174
  script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [end - 3, end]);
1456
- } else if (isTag(node)) {
1457
- if (parent.type === "expression") {
1458
- const siblings = parent.children.filter((n) => n.type !== "text" || n.value.trim());
1459
- const index = siblings.indexOf(node);
1460
- const before = siblings[index - 1];
1461
- if (!before || !isTag(before)) {
1462
- const after = siblings[index + 1];
1463
- if (after && (isTag(after) || after.type === "comment")) {
1464
- const start = node.position.start.offset;
1175
+ } else if (isJSXElementOrFragment(node)) {
1176
+ const start = node.start;
1177
+ script.appendOriginal(start);
1178
+ if (!fragmentOpened) openRootFragment(start);
1179
+ if (node.type === "JSXFragment") {
1180
+ if (isSyntheticFragment(node)) {
1181
+ const start = node.start;
1182
+ script.appendOriginal(start);
1183
+ script.appendVirtualScript("<>");
1184
+ script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1185
+ if (scriptNode.range[0] === start && scriptNode.type === AST_NODE_TYPES.JSXFragment) {
1186
+ delete scriptNode.openingFragment;
1187
+ delete scriptNode.closingFragment;
1188
+ const fragmentNode = scriptNode;
1189
+ fragmentNode.type = "AstroFragment";
1190
+ const last = fragmentNode.children[fragmentNode.children.length - 1];
1191
+ if (last && fragmentNode.range[1] < last.range[1]) {
1192
+ fragmentNode.range[1] = last.range[1];
1193
+ fragmentNode.loc.end = ctx.getLocFromIndex(fragmentNode.range[1]);
1194
+ }
1195
+ return true;
1196
+ }
1197
+ return false;
1198
+ });
1199
+ }
1200
+ } else {
1201
+ const tagType = getTagType(node);
1202
+ for (const attr of node.openingElement.attributes) {
1203
+ const analyzed = analyzeAttribute(attr);
1204
+ if (analyzed.kind === "literal-value" || analyzed.kind === "empty" || analyzed.kind === "expression" || analyzed.kind === "template-literal") {
1205
+ const attrName = getJsxName(analyzed.node.name);
1206
+ if (tagType === "component" ? /[.:@]/u.test(attrName) : /[.@]/u.test(attrName) || attrName.startsWith(":")) processAttributePunctuators(analyzed.node);
1207
+ }
1208
+ if (analyzed.kind === "literal-value") {
1209
+ const raw = code.slice(analyzed.node.value.start, analyzed.node.value.end);
1210
+ if (raw && !raw.startsWith("\"") && !raw.startsWith("'")) {
1211
+ const attrStart = analyzed.node.start;
1212
+ const valueStart = analyzed.node.value.start;
1213
+ const attrEnd = analyzed.node.end;
1214
+ script.appendOriginal(valueStart);
1215
+ script.appendVirtualScript("\"");
1216
+ script.appendOriginal(attrEnd);
1217
+ script.appendVirtualScript("\"");
1218
+ script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
1219
+ if (scriptNode.type === AST_NODE_TYPES.JSXAttribute && scriptNode.range[0] === attrStart) {
1220
+ const attrNode = scriptNode;
1221
+ if (attrNode.value?.type === "Literal" && typeof attrNode.value.value === "string") {
1222
+ const raw = code.slice(valueStart, attrEnd);
1223
+ attrNode.value.raw = raw;
1224
+ context.findToken(valueStart).value = raw;
1225
+ return true;
1226
+ }
1227
+ }
1228
+ return false;
1229
+ });
1230
+ }
1231
+ } else if (analyzed.kind === "shorthand") {
1232
+ const attrName = getJsxName(analyzed.node.name);
1233
+ const start = getShorthandAttributeOpeningBraceOffset(analyzed.node);
1465
1234
  script.appendOriginal(start);
1466
- script.appendVirtualScript("<>");
1235
+ const jsxName = /[\s"'[\]{}]/u.test(attrName) ? generateUniqueId(attrName) : attrName;
1236
+ script.appendVirtualScript(`${jsxName}=`);
1467
1237
  script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1468
- if (scriptNode.range[0] === start && scriptNode.type === AST_NODE_TYPES.JSXFragment) {
1469
- delete scriptNode.openingFragment;
1470
- delete scriptNode.closingFragment;
1471
- const fragmentNode = scriptNode;
1472
- fragmentNode.type = "AstroFragment";
1473
- const last = fragmentNode.children[fragmentNode.children.length - 1];
1474
- if (fragmentNode.range[1] < last.range[1]) {
1475
- fragmentNode.range[1] = last.range[1];
1476
- fragmentNode.loc.end = ctx.getLocFromIndex(fragmentNode.range[1]);
1477
- }
1238
+ if (scriptNode.type === AST_NODE_TYPES.JSXAttribute && scriptNode.range[0] === start) {
1239
+ const attrNode = scriptNode;
1240
+ attrNode.type = "AstroShorthandAttribute";
1241
+ const locs = ctx.getLocations(...attrNode.value.expression.range);
1242
+ if (jsxName !== attrName) attrNode.name.name = attrName;
1243
+ attrNode.name.range = locs.range;
1244
+ attrNode.name.loc = locs.loc;
1478
1245
  return true;
1479
1246
  }
1480
1247
  return false;
1481
1248
  });
1482
- }
1483
- }
1484
- }
1485
- const start = node.position.start.offset;
1486
- script.appendOriginal(start);
1487
- if (!fragmentOpened) openRootFragment(start);
1488
- for (const attr of node.attributes) {
1489
- if (attr.kind === "quoted" || attr.kind === "empty" || attr.kind === "expression" || attr.kind === "template-literal") {
1490
- if (node.type === "component" || node.type === "fragment" ? /[.:@]/u.test(attr.name) : /[.@]/u.test(attr.name) || attr.name.startsWith(":")) processAttributePunctuators(attr);
1491
- }
1492
- if (attr.kind === "quoted") {
1493
- if (attr.raw && !attr.raw.startsWith("\"") && !attr.raw.startsWith("'")) {
1494
- const attrStart = attr.position.start.offset;
1495
- const start = calcAttributeValueStartOffset(attr, ctx);
1496
- const end = calcAttributeEndOffset(attr, ctx);
1497
- script.appendOriginal(start);
1498
- script.appendVirtualScript("\"");
1499
- script.appendOriginal(end);
1500
- script.appendVirtualScript("\"");
1501
- script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
1249
+ } else if (analyzed.kind === "template-literal") {
1250
+ const attrStart = analyzed.node.start;
1251
+ const valueStart = analyzed.node.value.start;
1252
+ const attrEnd = analyzed.node.end;
1253
+ script.appendOriginal(valueStart);
1254
+ script.appendVirtualScript("{");
1255
+ script.appendOriginal(attrEnd);
1256
+ script.appendVirtualScript("}");
1257
+ script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1502
1258
  if (scriptNode.type === AST_NODE_TYPES.JSXAttribute && scriptNode.range[0] === attrStart) {
1503
1259
  const attrNode = scriptNode;
1504
- if (attrNode.value?.type === "Literal" && typeof attrNode.value.value === "string") {
1505
- const raw = ctx.code.slice(start, end);
1506
- attrNode.value.raw = raw;
1507
- context.findToken(start).value = raw;
1508
- return true;
1509
- }
1260
+ attrNode.type = "AstroTemplateLiteralAttribute";
1261
+ return true;
1510
1262
  }
1511
1263
  return false;
1512
1264
  });
1513
1265
  }
1514
- } else if (attr.kind === "shorthand") {
1515
- const start = attr.position.start.offset;
1516
- script.appendOriginal(start);
1517
- const jsxName = /[\s"'[\]{}]/u.test(attr.name) ? generateUniqueId(attr.name) : attr.name;
1518
- script.appendVirtualScript(`${jsxName}=`);
1519
- script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1520
- if (scriptNode.type === AST_NODE_TYPES.JSXAttribute && scriptNode.range[0] === start) {
1521
- const attrNode = scriptNode;
1522
- attrNode.type = "AstroShorthandAttribute";
1523
- const locs = ctx.getLocations(...attrNode.value.expression.range);
1524
- if (jsxName !== attr.name) attrNode.name.name = attr.name;
1525
- attrNode.name.range = locs.range;
1526
- attrNode.name.loc = locs.loc;
1527
- return true;
1528
- }
1529
- return false;
1530
- });
1531
- } else if (attr.kind === "template-literal") {
1532
- const attrStart = attr.position.start.offset;
1533
- const start = calcAttributeValueStartOffset(attr, ctx);
1534
- const end = calcAttributeEndOffset(attr, ctx);
1535
- script.appendOriginal(start);
1536
- script.appendVirtualScript("{");
1537
- script.appendOriginal(end);
1538
- script.appendVirtualScript("}");
1539
- script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1540
- if (scriptNode.type === AST_NODE_TYPES.JSXAttribute && scriptNode.range[0] === attrStart) {
1541
- const attrNode = scriptNode;
1542
- attrNode.type = "AstroTemplateLiteralAttribute";
1543
- return true;
1544
- }
1545
- return false;
1546
- });
1547
1266
  }
1548
- }
1549
- const closing = getSelfClosingTag(node, ctx);
1550
- if (closing && closing.end === ">") {
1551
- script.appendOriginal(closing.offset - 1);
1552
- script.appendVirtualScript("/");
1553
- }
1554
- if (node.name === "script" || node.name === "style" || node.attributes.some((attr) => attr.name === "is:raw")) {
1555
- const text = node.children[0];
1556
- if (text && text.type === "text") {
1557
- const styleNodeStart = node.position.start.offset;
1558
- const start = text.position.start.offset;
1559
- script.appendOriginal(start);
1560
- script.skipOriginalOffset(text.value.length);
1561
- script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1562
- if (scriptNode.type === AST_NODE_TYPES.JSXElement && scriptNode.range[0] === styleNodeStart) {
1563
- scriptNode.children = [{
1564
- type: "AstroRawText",
1565
- value: text.value,
1566
- raw: text.value,
1567
- parent: scriptNode,
1568
- ...ctx.getLocations(start, start + text.value.length)
1569
- }];
1570
- return true;
1571
- }
1572
- return false;
1573
- });
1574
- script.restoreContext.addToken(AST_TOKEN_TYPES.JSXText, [start, start + text.value.length]);
1267
+ const closing = getSelfClosingTag(node);
1268
+ if (closing && closing.end === ">") {
1269
+ script.appendOriginal(closing.offset - 1);
1270
+ script.appendVirtualScript("/");
1271
+ }
1272
+ const tagName = getJsxName(node.openingElement.name);
1273
+ if (tagName === "script" || tagName === "style" || node.openingElement.attributes.some((attr) => {
1274
+ const analyzed = analyzeAttribute(attr);
1275
+ if (analyzed.kind === "spread") return false;
1276
+ return getJsxName(analyzed.node.name) === "is:raw";
1277
+ })) {
1278
+ const text = getRawTextContent(node);
1279
+ if (text && text.value) {
1280
+ const styleNodeStart = node.start;
1281
+ script.appendOriginal(text.start);
1282
+ script.skipOriginalOffset(text.value.length);
1283
+ script.restoreContext.addRestoreNodeProcess((scriptNode) => {
1284
+ if (scriptNode.type === AST_NODE_TYPES.JSXElement && scriptNode.range[0] === styleNodeStart) {
1285
+ scriptNode.children = [{
1286
+ type: "AstroRawText",
1287
+ value: text.value,
1288
+ raw: text.value,
1289
+ parent: scriptNode,
1290
+ ...ctx.getLocations(text.start, text.end)
1291
+ }];
1292
+ return true;
1293
+ }
1294
+ return false;
1295
+ });
1296
+ script.restoreContext.addToken(AST_TOKEN_TYPES.JSXText, [text.start, text.end]);
1297
+ }
1575
1298
  }
1576
1299
  }
1577
- } else if (node.type === "comment") {
1578
- const start = node.position.start.offset;
1579
- const end = getEndOffset(node, ctx);
1300
+ } else if (node.type === "AstroComment") {
1301
+ const start = node.start;
1302
+ const end = node.end;
1580
1303
  const length = end - start;
1581
1304
  script.appendOriginal(start);
1582
1305
  if (!fragmentOpened) openRootFragment(start);
@@ -1600,9 +1323,9 @@ function processTemplate(ctx, resultTemplate) {
1600
1323
  return false;
1601
1324
  });
1602
1325
  script.restoreContext.addToken("HTMLComment", [start, start + length]);
1603
- } else if (node.type === "doctype") {
1604
- const start = node.position.start.offset;
1605
- const end = getEndOffset(node, ctx);
1326
+ } else if (node.type === "AstroDoctype") {
1327
+ const start = node.start;
1328
+ const end = node.end;
1606
1329
  const length = end - start;
1607
1330
  script.appendOriginal(start);
1608
1331
  if (!fragmentOpened) openRootFragment(start);
@@ -1626,63 +1349,139 @@ function processTemplate(ctx, resultTemplate) {
1626
1349
  });
1627
1350
  script.restoreContext.addToken("HTMLDocType", [start, end]);
1628
1351
  } else {
1629
- const start = node.position.start.offset;
1352
+ const start = node.start;
1630
1353
  script.appendOriginal(start);
1631
1354
  if (!fragmentOpened) openRootFragment(start);
1632
1355
  }
1633
- }, (node, [parent]) => {
1634
- if (isTag(node)) {
1635
- if (!getSelfClosingTag(node, ctx)) {
1636
- if (!getEndTag(node, ctx)) {
1637
- const offset = calcContentEndOffset(node, ctx);
1638
- script.appendOriginal(offset);
1639
- script.appendVirtualScript(`</${node.name}>`);
1640
- script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
1641
- const parent = context.getParent(scriptNode);
1642
- if (scriptNode.range[0] === offset && scriptNode.type === AST_NODE_TYPES.JSXClosingElement && parent.type === AST_NODE_TYPES.JSXElement) {
1643
- removeAllScopeAndVariableAndReference(scriptNode, {
1644
- visitorKeys: context.result.visitorKeys,
1645
- scopeManager: context.result.scopeManager
1646
- });
1647
- parent.closingElement = null;
1648
- return true;
1649
- }
1650
- return false;
1651
- });
1652
- }
1356
+ }, (node) => {
1357
+ if (node.type === "JSXElement") {
1358
+ if (!getSelfClosingTag(node) && node.closingElement == null) {
1359
+ const offset = calcContentEndOffset(node);
1360
+ script.appendOriginal(offset);
1361
+ script.appendVirtualScript(`</${getJsxName(node.openingElement.name)}>`);
1362
+ script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
1363
+ const parent = context.getParent(scriptNode);
1364
+ if (scriptNode.range[0] === offset && scriptNode.type === AST_NODE_TYPES.JSXClosingElement && parent.type === AST_NODE_TYPES.JSXElement) {
1365
+ removeAllScopeAndVariableAndReference(scriptNode, {
1366
+ visitorKeys: context.result.visitorKeys,
1367
+ scopeManager: context.result.scopeManager
1368
+ });
1369
+ parent.closingElement = null;
1370
+ return true;
1371
+ }
1372
+ return false;
1373
+ });
1653
1374
  }
1654
1375
  }
1655
- if ((isTag(node) || node.type === "comment") && parent.type === "expression") {
1656
- const siblings = parent.children.filter((n) => n.type !== "text" || n.value.trim());
1657
- const index = siblings.indexOf(node);
1658
- const after = siblings[index + 1];
1659
- if (!after || !isTag(after) && after.type !== "comment") {
1660
- const before = siblings[index - 1];
1661
- if (before && (isTag(before) || before.type === "comment")) {
1662
- const end = getEndOffset(node, ctx);
1663
- script.appendOriginal(end);
1664
- script.appendVirtualScript("</>");
1665
- }
1666
- }
1376
+ if (node.type === "JSXFragment" && isSyntheticFragment(node)) {
1377
+ script.appendOriginal(node.end);
1378
+ script.appendVirtualScript("</>");
1667
1379
  }
1668
1380
  });
1669
1381
  if (fragmentOpened) {
1670
- const last = resultTemplate.ast.children[resultTemplate.ast.children.length - 1];
1671
- const end = getEndOffset(last, ctx);
1672
- script.appendOriginal(end);
1382
+ const last = findLastJSXNode(resultTemplate.ast);
1383
+ if (last) script.appendOriginal(last.end);
1673
1384
  script.appendVirtualScript("</>;");
1674
1385
  }
1675
- script.appendOriginal(ctx.code.length);
1386
+ script.appendOriginal(code.length);
1676
1387
  return script;
1677
1388
  /**
1389
+ * Walk template nodes in source order from the compiler AST.
1390
+ *
1391
+ * The traversal starts with `AstroRoot`. Root-only source ranges such as
1392
+ * frontmatter fences are handled in the root branch so they do not become
1393
+ * node-shaped intermediate children.
1394
+ */
1395
+ function walkElements(parent, enter, leave) {
1396
+ const nodes = [...parent.body];
1397
+ const frontmatter = parent.frontmatter;
1398
+ if (frontmatter && !isEmptyFrontmatter(frontmatter)) {
1399
+ let insertIndex = nodes.findIndex((child) => frontmatter.start <= child.start);
1400
+ if (insertIndex < 0) insertIndex = nodes.length;
1401
+ while (insertIndex > 0 && isWhitespaceJSXText(nodes[insertIndex - 1])) {
1402
+ nodes.splice(insertIndex - 1, 1);
1403
+ insertIndex--;
1404
+ }
1405
+ while (insertIndex < nodes.length && isWhitespaceJSXText(nodes[insertIndex])) nodes.splice(insertIndex, 1);
1406
+ nodes.splice(insertIndex, 0, frontmatter);
1407
+ }
1408
+ while (nodes.length > 0 && isWhitespaceJSXText(nodes[0])) nodes.shift();
1409
+ while (nodes.length > 0 && isWhitespaceJSXText(nodes[nodes.length - 1])) nodes.pop();
1410
+ for (const child of nodes) walkChild(child, enter, leave);
1411
+ }
1412
+ /** Get raw text content for script, style, and raw nodes. */
1413
+ function getRawTextContent(node) {
1414
+ if (node.closingElement == null) return null;
1415
+ const start = node.openingElement.end;
1416
+ const end = node.closingElement.start;
1417
+ if (start >= end) return null;
1418
+ return {
1419
+ start,
1420
+ end,
1421
+ value: code.slice(start, end)
1422
+ };
1423
+ }
1424
+ /** Get self-closing tag metadata. */
1425
+ function getSelfClosingTag(node) {
1426
+ if (!node.openingElement?.selfClosing || node.closingElement) return null;
1427
+ const offset = node.openingElement.end;
1428
+ return {
1429
+ offset,
1430
+ end: code.startsWith("/>", offset - 2) ? "/>" : ">"
1431
+ };
1432
+ }
1433
+ /** Get the Astro attribute kind represented by a compiler node. */
1434
+ function analyzeAttribute(attr) {
1435
+ if (attr.type === "JSXSpreadAttribute") return {
1436
+ kind: "spread",
1437
+ node: attr
1438
+ };
1439
+ if (!attr.value) return {
1440
+ kind: "empty",
1441
+ node: attr
1442
+ };
1443
+ if (isShorthandAttribute(attr, code)) return {
1444
+ kind: "shorthand",
1445
+ node: attr
1446
+ };
1447
+ if (attr.value.type === "Literal") return {
1448
+ kind: "literal-value",
1449
+ node: attr
1450
+ };
1451
+ if (attr.value.type === "JSXExpressionContainer" && code[attr.value.start] === "`") return {
1452
+ kind: "template-literal",
1453
+ node: attr
1454
+ };
1455
+ return {
1456
+ kind: "expression",
1457
+ node: attr
1458
+ };
1459
+ }
1460
+ /**
1461
+ * Get the source offset of the opening `{` for an Astro shorthand attribute.
1462
+ *
1463
+ * The compiler represents `<img {src}>` as a JSXAttribute that starts at
1464
+ * `src`, while the end offset still includes `}`. The virtual JSX needs to
1465
+ * insert `src=` before the original `{src}`, so shorthand processing must use
1466
+ * the brace offset instead of `attr.start`.
1467
+ */
1468
+ function getShorthandAttributeOpeningBraceOffset(attr) {
1469
+ return code[attr.start - 1] === "{" ? attr.start - 1 : attr.start;
1470
+ }
1471
+ /** Check whether a node is whitespace-only text. */
1472
+ function isWhitespaceJSXText(node) {
1473
+ return node.type === "JSXText" && code.slice(node.start, node.end).trim() === "";
1474
+ }
1475
+ /**
1678
1476
  * Process for attribute punctuators
1679
1477
  */
1680
1478
  function processAttributePunctuators(attr) {
1681
- const start = attr.position.start.offset;
1479
+ const name = getJsxName(attr.name);
1480
+ const start = attr.name.start;
1682
1481
  let targetIndex = start;
1683
1482
  let colonOffset;
1684
- for (let index = 0; index < attr.name.length; index++) {
1685
- const char = attr.name[index];
1483
+ for (let index = 0; index < name.length; index++) {
1484
+ const char = name[index];
1686
1485
  if (char !== ":" && char !== "." && char !== "@") continue;
1687
1486
  if (index === 0) targetIndex++;
1688
1487
  const punctuatorIndex = start + index;
@@ -1695,8 +1494,8 @@ function processTemplate(ctx, resultTemplate) {
1695
1494
  const punctuatorIndex = start + colonOffset;
1696
1495
  script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [start, punctuatorIndex]);
1697
1496
  script.restoreContext.addToken(AST_TOKEN_TYPES.Punctuator, [punctuatorIndex, punctuatorIndex + 1]);
1698
- script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [punctuatorIndex + 1, start + attr.name.length]);
1699
- } else script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [start, start + attr.name.length]);
1497
+ script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [punctuatorIndex + 1, start + name.length]);
1498
+ } else script.restoreContext.addToken(AST_TOKEN_TYPES.JSXIdentifier, [start, start + name.length]);
1700
1499
  script.restoreContext.addRestoreNodeProcess((scriptNode, context) => {
1701
1500
  if (scriptNode.type === AST_NODE_TYPES.JSXAttribute && scriptNode.range[0] === targetIndex) {
1702
1501
  const baseNameNode = scriptNode.name;
@@ -1705,25 +1504,27 @@ function processTemplate(ctx, resultTemplate) {
1705
1504
  nameNode.type = AST_NODE_TYPES.JSXNamespacedName;
1706
1505
  nameNode.namespace = {
1707
1506
  type: AST_NODE_TYPES.JSXIdentifier,
1708
- name: attr.name.slice(0, colonOffset),
1709
- ...ctx.getLocations(baseNameNode.range[0], baseNameNode.range[0] + colonOffset)
1507
+ name: name.slice(0, colonOffset),
1508
+ ...ctx.getLocations(baseNameNode.range[0], baseNameNode.range[0] + colonOffset),
1509
+ parent: void 0
1710
1510
  };
1711
1511
  nameNode.name = {
1712
1512
  type: AST_NODE_TYPES.JSXIdentifier,
1713
- name: attr.name.slice(colonOffset + 1),
1714
- ...ctx.getLocations(baseNameNode.range[0] + colonOffset + 1, baseNameNode.range[1])
1513
+ name: name.slice(colonOffset + 1),
1514
+ ...ctx.getLocations(baseNameNode.range[0] + colonOffset + 1, baseNameNode.range[1]),
1515
+ parent: void 0
1715
1516
  };
1716
1517
  scriptNode.name = nameNode;
1717
1518
  nameNode.namespace.parent = nameNode;
1718
1519
  nameNode.name.parent = nameNode;
1719
1520
  } else if (baseNameNode.type === AST_NODE_TYPES.JSXIdentifier) {
1720
1521
  const nameNode = baseNameNode;
1721
- nameNode.name = attr.name;
1522
+ nameNode.name = name;
1722
1523
  scriptNode.name = nameNode;
1723
1524
  } else {
1724
1525
  const nameNode = baseNameNode;
1725
- nameNode.namespace.name = attr.name.slice(baseNameNode.namespace.range[0] - start, baseNameNode.namespace.range[1] - start);
1726
- nameNode.name.name = attr.name.slice(baseNameNode.name.range[0] - start, baseNameNode.name.range[1] - start);
1526
+ nameNode.namespace.name = name.slice(baseNameNode.namespace.range[0] - start, baseNameNode.namespace.range[1] - start);
1527
+ nameNode.name.name = name.slice(baseNameNode.name.range[0] - start, baseNameNode.name.range[1] - start);
1727
1528
  scriptNode.name = nameNode;
1728
1529
  nameNode.namespace.parent = nameNode;
1729
1530
  nameNode.name.parent = nameNode;
@@ -1739,17 +1540,90 @@ function processTemplate(ctx, resultTemplate) {
1739
1540
  */
1740
1541
  function generateUniqueId(base) {
1741
1542
  let candidate = `$_${base.replace(/\W/g, "_")}${uniqueIdSeq++}`;
1742
- while (usedUniqueIds.has(candidate) || ctx.code.includes(candidate)) candidate = `$_${base.replace(/\W/g, "_")}${uniqueIdSeq++}`;
1543
+ while (usedUniqueIds.has(candidate) || code.includes(candidate)) candidate = `$_${base.replace(/\W/g, "_")}${uniqueIdSeq++}`;
1743
1544
  usedUniqueIds.add(candidate);
1744
1545
  return candidate;
1745
1546
  }
1547
+ /**
1548
+ * Find the last JSXNode.
1549
+ * Basically, it returns the last element of AstroRootNode.body.
1550
+ * However, if the last element is a JSXTextNode, and its text is whitespace,
1551
+ * and all preceding elements are AstroCommentNode, it returns the last AstroCommentNode.
1552
+ */
1553
+ function findLastJSXNode(ast) {
1554
+ const body = ast.body;
1555
+ if (body.length === 0) return null;
1556
+ const lastNode = body[body.length - 1];
1557
+ if (isWhitespaceJSXText(lastNode) && body.length >= 2 && body.slice(0, -1).every((node) => node.type === "AstroComment")) return body[body.length - 2];
1558
+ return lastNode;
1559
+ }
1560
+ }
1561
+ /** Walk one compiler child node. */
1562
+ function walkChild(node, enter, leave) {
1563
+ enter(node);
1564
+ if (isJSXElementOrFragment(node)) for (const child of node.children) {
1565
+ if (child.type === "AstroScript") continue;
1566
+ walkChild(child, enter, leave);
1567
+ }
1568
+ else if (node.type === "JSXExpressionContainer") walkExpression(node.expression, enter, leave);
1569
+ leave(node);
1570
+ }
1571
+ /** Walk one compiler expression node. */
1572
+ function walkExpression(node, enter, leave) {
1573
+ const walked = /* @__PURE__ */ new Set();
1574
+ walk(node, (child, _parents, ctx) => {
1575
+ if (walked.has(child)) return;
1576
+ walked.add(child);
1577
+ if (isWalkableNode(child)) {
1578
+ walkChild(child, enter, leave);
1579
+ ctx.skipChildren();
1580
+ }
1581
+ });
1582
+ }
1583
+ /**
1584
+ * Check whether the given node is a walkable template node that should be passed through the enter/leave hooks.
1585
+ */
1586
+ function isWalkableNode(node) {
1587
+ return isJSXElementOrFragment(node) || node.type === "AstroComment" || node.type === "AstroDoctype" || node.type === "JSXExpressionContainer" || node.type === "JSXText";
1588
+ }
1589
+ /**
1590
+ * Check whether the frontmatter is empty (i.e. contains no characters).
1591
+ * In this case, the frontmatter node is still generated by the compiler,
1592
+ * but it has no source range. We can identify such empty frontmatter by checking if the start and end offsets are the same.
1593
+ */
1594
+ function isEmptyFrontmatter(node) {
1595
+ return node.start === node.end;
1596
+ }
1597
+ /** Convert a compiler JSX name node to source text. */
1598
+ function getJsxName(nameNode) {
1599
+ if (nameNode.type === "JSXIdentifier") return nameNode.name;
1600
+ if (nameNode.type === "JSXMemberExpression") return `${getJsxName(nameNode.object)}.${getJsxName(nameNode.property)}`;
1601
+ if (nameNode.type === "JSXNamespacedName") return `${getJsxName(nameNode.namespace)}:${getJsxName(nameNode.name)}`;
1602
+ return "";
1603
+ }
1604
+ /** Get the Astro tag category for a traversal node. */
1605
+ function getTagType(node) {
1606
+ const name = getJsxName(node.openingElement.name);
1607
+ if (/^[A-Z]/u.test(name) || name.includes(".")) return "component";
1608
+ if (name.includes("-")) return "custom-element";
1609
+ return "element";
1610
+ }
1611
+ /** Calculate where an element without a closing tag should end. */
1612
+ function calcContentEndOffset(node) {
1613
+ const children = node.children;
1614
+ const lastChild = children[children.length - 1];
1615
+ if (lastChild) return lastChild.end;
1616
+ return node.openingElement.end;
1746
1617
  }
1747
1618
  //#endregion
1748
1619
  //#region src/context/index.ts
1749
1620
  var Context = class {
1621
+ code;
1622
+ filePath;
1623
+ locs;
1624
+ locsMap = /* @__PURE__ */ new Map();
1625
+ state = {};
1750
1626
  constructor(code, filePath) {
1751
- this.locsMap = /* @__PURE__ */ new Map();
1752
- this.state = {};
1753
1627
  this.locs = new LinesAndColumns(code);
1754
1628
  this.code = code;
1755
1629
  this.filePath = filePath;
@@ -1804,31 +1678,19 @@ var Context = class {
1804
1678
  }
1805
1679
  };
1806
1680
  var LinesAndColumns = class {
1681
+ lineStartIndices;
1682
+ code;
1807
1683
  constructor(origCode) {
1808
1684
  const len = origCode.length;
1809
1685
  const lineStartIndices = [0];
1810
- const crs = [];
1811
- let normalizedCode = "";
1812
1686
  for (let index = 0; index < len;) {
1813
1687
  const c = origCode[index++];
1814
- if (c === "\r") {
1815
- const next = origCode[index++] || "";
1816
- if (next === "\n") {
1817
- normalizedCode += next;
1818
- crs.push(index - 2);
1819
- lineStartIndices.push(index);
1820
- } else {
1821
- normalizedCode += `\n${next}`;
1822
- lineStartIndices.push(index - 1);
1823
- }
1824
- } else {
1825
- normalizedCode += c;
1826
- if (c === "\n") lineStartIndices.push(index);
1827
- }
1688
+ if (c === "\r") if ((origCode[index++] || "") === "\n") lineStartIndices.push(index);
1689
+ else lineStartIndices.push(index - 1);
1690
+ else if (c === "\n") lineStartIndices.push(index);
1828
1691
  }
1829
1692
  this.lineStartIndices = lineStartIndices;
1830
1693
  this.code = origCode;
1831
- this.normalizedLineFeed = new NormalizedLineFeed(normalizedCode, crs);
1832
1694
  }
1833
1695
  getLocFromIndex(index) {
1834
1696
  const lineNumber = sortedLastIndex(this.lineStartIndices, (target) => target - index);
@@ -1843,264 +1705,145 @@ var LinesAndColumns = class {
1843
1705
  else if (this.lineStartIndices.length === lineIndex) return this.code.length + loc.column;
1844
1706
  return this.code.length + loc.column;
1845
1707
  }
1846
- getNormalizedLineFeed() {
1847
- return this.normalizedLineFeed;
1848
- }
1849
- };
1850
- var NormalizedLineFeed = class {
1851
- get needRemap() {
1852
- return this.offsets.length > 0;
1853
- }
1854
- constructor(code, offsets) {
1855
- this.code = code;
1856
- this.offsets = offsets;
1857
- if (offsets.length) {
1858
- const cache = {};
1859
- this.remapIndex = (index) => {
1860
- let result = cache[index];
1861
- if (result != null) return result;
1862
- result = index;
1863
- for (const offset of offsets) if (offset < result) result++;
1864
- else break;
1865
- return cache[index] = result;
1866
- };
1867
- } else this.remapIndex = (i) => i;
1868
- }
1869
1708
  };
1870
1709
  //#endregion
1871
- //#region src/parser/astro-parser/parse.ts
1872
- /**
1873
- * Parse code by `@astrojs/compiler`
1874
- */
1875
- function parse(code, ctx) {
1876
- const result = service.parse(code, { position: true });
1877
- for (const { code, text, location, severity } of result.diagnostics || []) if (severity === 1) {
1878
- ctx.originalAST = result.ast;
1879
- throw new ParseError(`${text} [${code}]`, location, ctx);
1880
- }
1881
- if (!result.ast.children) result.ast.children = [];
1882
- const htmlElement = result.ast.children.find((n) => n.type === "element" && n.name === "html");
1883
- if (!result._adjusted) {
1884
- if (htmlElement) adjustHTML(result.ast, htmlElement, ctx);
1885
- fixLocations(result.ast, ctx);
1886
- result._adjusted = true;
1887
- }
1888
- return result;
1889
- }
1710
+ //#region src/errors.ts
1890
1711
  /**
1891
- * Adjust <html> element node
1712
+ * Astro parse errors.
1892
1713
  */
1893
- function adjustHTML(ast, htmlElement, ctx) {
1894
- const htmlEnd = ctx.code.indexOf("</html");
1895
- if (htmlEnd < 0) return;
1896
- const isOffsetAfter = buildComparableOffsetComparator(ctx.code);
1897
- const hasTokenAfter = Boolean(ctx.code.slice(htmlEnd + 7).trim());
1898
- const children = [...htmlElement.children];
1899
- for (const child of children) {
1900
- const offset = child.position?.start.offset;
1901
- if (hasTokenAfter && offset != null) {
1902
- if (isOffsetAfter(offset, htmlEnd)) {
1903
- htmlElement.children.splice(htmlElement.children.indexOf(child), 1);
1904
- ast.children.push(child);
1905
- }
1906
- }
1907
- if (child.type === "element" && child.name === "body") adjustHTMLBody(ast, htmlElement, htmlEnd, hasTokenAfter, child, ctx, isOffsetAfter);
1908
- }
1909
- /**
1910
- * Build a comparator used only for matching compiler offsets against
1911
- * positions derived from `ctx.code`.
1912
- *
1913
- * The raw offset check is important for laziness: if the compiler offset is
1914
- * already before the threshold, remapping cannot make it jump forward past
1915
- * that threshold, so we can reject it without any byte/code-unit work.
1916
- */
1917
- function buildComparableOffsetComparator(code) {
1918
- let remapOffset;
1919
- const comparableOffsetCache = /* @__PURE__ */ new Map();
1920
- return (offset, threshold) => {
1921
- if (threshold > offset) return false;
1922
- let comparableOffset = comparableOffsetCache.get(offset);
1923
- if (comparableOffset == null) {
1924
- remapOffset ||= buildComparableOffsetRemapper(code);
1925
- comparableOffset = remapOffset(offset);
1926
- comparableOffsetCache.set(offset, comparableOffset);
1927
- }
1928
- return threshold <= comparableOffset;
1929
- };
1930
- }
1714
+ var ParseError = class extends SyntaxError {
1715
+ index;
1716
+ lineNumber;
1717
+ column;
1718
+ originalAST;
1931
1719
  /**
1932
- * Build remapper used only for comparing compiler offsets with `ctx.code`.
1720
+ * Initialize this ParseError instance.
1933
1721
  */
1934
- function buildComparableOffsetRemapper(code) {
1935
- let byteOffsets, codeUnitOffsets;
1936
- for (let index = 0, byteOffset = 0; index < code.length;) {
1937
- const codePoint = code.codePointAt(index);
1938
- const nextIndex = index + (codePoint > 65535 ? 2 : 1);
1939
- const nextByteOffset = byteOffset + getUTF8ByteLength(codePoint);
1940
- if (byteOffsets) {
1941
- byteOffsets.push(nextByteOffset);
1942
- codeUnitOffsets.push(nextIndex);
1943
- } else if (codePoint > 127) {
1944
- byteOffsets = [0];
1945
- codeUnitOffsets = [0];
1946
- for (let asciiOffset = 1; asciiOffset <= index; asciiOffset++) {
1947
- byteOffsets.push(asciiOffset);
1948
- codeUnitOffsets.push(asciiOffset);
1949
- }
1950
- byteOffsets.push(nextByteOffset);
1951
- codeUnitOffsets.push(nextIndex);
1952
- }
1953
- index = nextIndex;
1954
- byteOffset = nextByteOffset;
1722
+ constructor(message, offset, ctx) {
1723
+ super(message);
1724
+ if (typeof offset === "number") {
1725
+ this.index = offset;
1726
+ const loc = ctx.getLocFromIndex(offset);
1727
+ this.lineNumber = loc.line;
1728
+ this.column = loc.column;
1729
+ } else {
1730
+ this.index = ctx.getIndexFromLoc(offset);
1731
+ this.lineNumber = offset.line;
1732
+ this.column = offset.column;
1955
1733
  }
1956
- if (!byteOffsets || !codeUnitOffsets) return (offset) => offset;
1957
- return (offset) => {
1958
- const index = sortedLastIndex(byteOffsets, (target) => target - offset) - 1;
1959
- return codeUnitOffsets[Math.max(index, 0)];
1960
- };
1961
- }
1962
- /**
1963
- * Get UTF-8 byte length for code point.
1964
- */
1965
- function getUTF8ByteLength(codePoint) {
1966
- if (codePoint <= 127) return 1;
1967
- if (codePoint <= 2047) return 2;
1968
- if (codePoint <= 65535) return 3;
1969
- return 4;
1734
+ this.originalAST = ctx.originalAST;
1970
1735
  }
1971
- }
1736
+ };
1737
+ //#endregion
1738
+ //#region src/parser/astro-parser/parse.ts
1972
1739
  /**
1973
- * Adjust <body> element node
1974
- */
1975
- function adjustHTMLBody(ast, htmlElement, htmlEnd, hasTokenAfterHtmlEnd, bodyElement, ctx, isOffsetAfter) {
1976
- const bodyEnd = ctx.code.indexOf("</body");
1977
- if (bodyEnd == null) return;
1978
- const hasTokenAfter = Boolean(ctx.code.slice(bodyEnd + 7, htmlEnd).trim());
1979
- if (!hasTokenAfter && !hasTokenAfterHtmlEnd) return;
1980
- const children = [...bodyElement.children];
1981
- for (const child of children) {
1982
- const offset = child.position?.start.offset;
1983
- if (offset != null && isOffsetAfter(offset, bodyEnd)) {
1984
- if (hasTokenAfterHtmlEnd && isOffsetAfter(offset, htmlEnd)) {
1985
- bodyElement.children.splice(bodyElement.children.indexOf(child), 1);
1986
- ast.children.push(child);
1987
- } else if (hasTokenAfter) {
1988
- bodyElement.children.splice(bodyElement.children.indexOf(child), 1);
1989
- htmlElement.children.push(child);
1990
- }
1991
- }
1740
+ * Parse code by `@astrojs/compiler-rs`.
1741
+ *
1742
+ * The compiler returns locations in its own offset format. Normalize them here
1743
+ * before any later parser phase reads the AST, so the rest of the parser can
1744
+ * treat every `start`/`end` and diagnostic label as ESLint-compatible source
1745
+ * indexes.
1746
+ */
1747
+ function parse$1(code, ctx) {
1748
+ const result = parse(code);
1749
+ normalizeLocations(result, code, ctx);
1750
+ for (const diagnostic of result.diagnostics || []) {
1751
+ if (diagnostic.severity !== "error") continue;
1752
+ ctx.originalAST = result.ast;
1753
+ const location = diagnostic.labels?.[0]?.start ?? 0;
1754
+ throw new ParseError(diagnostic.text, location, ctx);
1992
1755
  }
1756
+ return result;
1993
1757
  }
1994
1758
  /**
1995
- * Fix locations
1996
- */
1997
- function fixLocations(node, ctx) {
1998
- let start = 0;
1999
- walk(node, ctx.code, (node, [parent]) => {
2000
- if (node.type === "frontmatter") {
2001
- start = node.position.start.offset = tokenIndex(ctx, "---", start);
2002
- if (!node.position.end) node.position.end = {};
2003
- start = node.position.end.offset = tokenIndex(ctx, "---", start + 3 + node.value.length) + 3;
2004
- } else if (node.type === "fragment" || node.type === "element" || node.type === "component" || node.type === "custom-element") {
2005
- if (!node.position) node.position = {
2006
- start: {},
2007
- end: {}
2008
- };
2009
- start = node.position.start.offset = tokenIndex(ctx, "<", start);
2010
- start += 1;
2011
- start += node.name.length;
2012
- if (!node.attributes.length) start = calcStartTagEndOffset(node, ctx);
2013
- } else if (node.type === "attribute") {
2014
- fixLocationForAttr(node, ctx, start);
2015
- start = calcAttributeEndOffset(node, ctx);
2016
- if (node.position.end) node.position.end.offset = start;
2017
- } else if (node.type === "comment") {
2018
- node.position.start.offset = tokenIndex(ctx, "<!--", start);
2019
- start = calcCommentEndOffset(node, ctx);
2020
- if (node.position.end) node.position.end.offset = start;
2021
- } else if (node.type === "text") {
2022
- if (parent.type === "element" && (parent.name === "script" || parent.name === "style")) {
2023
- node.position.start.offset = start;
2024
- start = ctx.code.indexOf(`</${parent.name}`, start);
2025
- if (start < 0) start = ctx.code.length;
2026
- } else {
2027
- const index = tokenIndexSafe(ctx.code, node.value, start);
2028
- if (index != null) {
2029
- start = node.position.start.offset = index;
2030
- start += node.value.length;
2031
- } else {
2032
- node.position.start.offset = start;
2033
- const value = node.value.replace(/\s+/gu, "");
2034
- for (const char of value) start = tokenIndex(ctx, char, start) + 1;
2035
- start = skipSpaces(ctx.code, start);
2036
- node.value = ctx.code.slice(node.position.start.offset, start);
2037
- }
2038
- }
2039
- if (node.position.end) node.position.end.offset = start;
2040
- } else if (node.type === "expression") {
2041
- start = node.position.start.offset = tokenIndex(ctx, "{", start);
2042
- start += 1;
2043
- } else if (node.type === "doctype") {
2044
- if (!node.position) node.position = {
2045
- start: {},
2046
- end: {}
2047
- };
2048
- if (!node.position.end) node.position.end = {};
2049
- start = node.position.start.offset = tokenIndex(ctx, "<!", start);
2050
- start += 2;
2051
- start = node.position.end.offset = ctx.code.indexOf(">", start) + 1;
2052
- } else if (node.type === "root") {}
2053
- }, (node, [parent]) => {
2054
- if (node.type === "attribute") {
2055
- const attributes = parent.attributes;
2056
- if (attributes[attributes.length - 1] === node) start = calcStartTagEndOffset(parent, ctx);
2057
- } else if (node.type === "expression") start = tokenIndex(ctx, "}", start) + 1;
2058
- else if (node.type === "fragment" || node.type === "element" || node.type === "component" || node.type === "custom-element") {
2059
- if (!getSelfClosingTag(node, ctx)) {
2060
- const closeTagStart = tokenIndexSafe(ctx.code, `</${node.name}`, start);
2061
- if (closeTagStart != null) {
2062
- start = closeTagStart + 2 + node.name.length;
2063
- start = tokenIndex(ctx, ">", start) + 1;
2064
- }
2065
- }
2066
- } else return;
2067
- if (node.position.end) node.position.end.offset = start;
2068
- });
2069
- }
2070
- /**
2071
- * Fix locations
2072
- */
2073
- function fixLocationForAttr(node, ctx, start) {
2074
- if (node.kind === "empty") node.position.start.offset = tokenIndex(ctx, node.name, start);
2075
- else if (node.kind === "quoted") node.position.start.offset = tokenIndex(ctx, node.name, start);
2076
- else if (node.kind === "expression") node.position.start.offset = tokenIndex(ctx, node.name, start);
2077
- else if (node.kind === "shorthand") node.position.start.offset = tokenIndex(ctx, "{", start);
2078
- else if (node.kind === "spread") node.position.start.offset = tokenIndex(ctx, "{", start);
2079
- else if (node.kind === "template-literal") node.position.start.offset = tokenIndex(ctx, node.name, start);
2080
- else throw new ParseError(`Unknown attr kind: ${node.kind}`, node.position.start.offset, ctx);
2081
- }
2082
- /**
2083
- * Get token index
2084
- */
2085
- function tokenIndex(ctx, token, position) {
2086
- const index = tokenIndexSafe(ctx.code, token, position);
2087
- if (index == null) {
2088
- const start = token.trim() === token ? skipSpaces(ctx.code, position) : position;
2089
- throw new ParseError(`Unknown token at ${start}, expected: ${JSON.stringify(token)}, actual: ${JSON.stringify(ctx.code.slice(start, start + 10))}`, start, ctx);
1759
+ * Normalize compiler byte offsets to JavaScript string indices.
1760
+ *
1761
+ * `@astrojs/compiler-rs` reports `start`/`end` as UTF-8 byte offsets. ESLint
1762
+ * `range`, `Context`, and JavaScript string slicing all use UTF-16 code-unit
1763
+ * indexes. Those values are the same for ASCII, but diverge as soon as a
1764
+ * multibyte character appears before or inside a node. For example, the raw
1765
+ * compiler offset after `""` is 3, while the JavaScript index is 1.
1766
+ *
1767
+ * Keep this conversion in the parse phase so downstream code does not need to
1768
+ * know which compiler produced the AST or remember to remap every lookup.
1769
+ */
1770
+ function normalizeLocations(result, code, ctx) {
1771
+ const byteOffsetToIndex = buildByteOffsetToIndexMap(code);
1772
+ remapNodeLocations(result.ast, byteOffsetToIndex);
1773
+ for (const diagnostic of result.diagnostics || []) for (const label of diagnostic.labels || []) {
1774
+ label.start = byteOffsetToIndex(label.start);
1775
+ label.end = byteOffsetToIndex(label.end);
1776
+ const loc = ctx.getLocFromIndex(label.start);
1777
+ label.line = loc.line;
1778
+ label.column = loc.column;
1779
+ }
1780
+ }
1781
+ /**
1782
+ * Remap start/end properties on a compiler AST subtree.
1783
+ *
1784
+ * The compiler AST contains ESTree-compatible JavaScript nodes nested inside
1785
+ * Astro nodes. Walking generically keeps the location fix independent from the
1786
+ * exact node shape and prevents future compiler node additions from bypassing
1787
+ * the normalization.
1788
+ */
1789
+ function remapNodeLocations(value, byteOffsetToIndex, seen = /* @__PURE__ */ new Set()) {
1790
+ if (!value || typeof value !== "object") return;
1791
+ if (seen.has(value)) return;
1792
+ seen.add(value);
1793
+ if (Array.isArray(value)) {
1794
+ for (const child of value) remapNodeLocations(child, byteOffsetToIndex, seen);
1795
+ return;
2090
1796
  }
2091
- return index;
1797
+ const node = value;
1798
+ if (typeof node.start === "number") node.start = byteOffsetToIndex(node.start);
1799
+ if (typeof node.end === "number") node.end = byteOffsetToIndex(node.end);
1800
+ for (const child of Object.values(node)) remapNodeLocations(child, byteOffsetToIndex, seen);
1801
+ }
1802
+ /**
1803
+ * Build a mapper from compiler byte offsets to JavaScript string indices.
1804
+ *
1805
+ * The table records both coordinates at each source character boundary. The
1806
+ * returned function can then translate any compiler offset with a binary
1807
+ * search. If the offset falls between boundaries, it returns the previous
1808
+ * JavaScript index; this keeps error locations stable even if the compiler
1809
+ * points into a multibyte character boundary.
1810
+ */
1811
+ function buildByteOffsetToIndexMap(source) {
1812
+ const byteOffsets = [0];
1813
+ const codeUnitOffsets = [0];
1814
+ let byteOffset = 0;
1815
+ for (let index = 0; index < source.length;) {
1816
+ const codePoint = source.codePointAt(index);
1817
+ const nextIndex = index + (codePoint > 65535 ? 2 : 1);
1818
+ byteOffset += getUTF8ByteLength(codePoint);
1819
+ byteOffsets.push(byteOffset);
1820
+ codeUnitOffsets.push(nextIndex);
1821
+ index = nextIndex;
1822
+ }
1823
+ return (offset) => {
1824
+ let low = 0;
1825
+ let high = byteOffsets.length - 1;
1826
+ while (low <= high) {
1827
+ const mid = Math.floor((low + high) / 2);
1828
+ const value = byteOffsets[mid];
1829
+ if (value === offset) return codeUnitOffsets[mid];
1830
+ if (value < offset) low = mid + 1;
1831
+ else high = mid - 1;
1832
+ }
1833
+ return codeUnitOffsets[Math.max(high, 0)] ?? 0;
1834
+ };
2092
1835
  }
2093
- /**
2094
- * Get token index
2095
- */
2096
- function tokenIndexSafe(string, token, position) {
2097
- const index = token.trim() === token ? skipSpaces(string, position) : position;
2098
- if (string.startsWith(token, index)) return index;
2099
- return null;
1836
+ /** Get the UTF-8 byte length for a Unicode code point. */
1837
+ function getUTF8ByteLength(codePoint) {
1838
+ if (codePoint <= 127) return 1;
1839
+ if (codePoint <= 2047) return 2;
1840
+ if (codePoint <= 65535) return 3;
1841
+ return 4;
2100
1842
  }
2101
1843
  //#endregion
2102
1844
  //#region src/parser/lru-cache.ts
2103
1845
  var LruCache = class extends Map {
1846
+ capacity;
2104
1847
  constructor(capacity) {
2105
1848
  super();
2106
1849
  this.capacity = capacity;
@@ -2134,64 +1877,22 @@ function parseTemplate$1(code, filePath) {
2134
1877
  const cache = lruCache.get(code);
2135
1878
  if (cache) return cache;
2136
1879
  const ctx = new Context(code, filePath);
2137
- const normalized = ctx.locs.getNormalizedLineFeed();
2138
- const ctxForAstro = normalized.needRemap ? new Context(normalized.code, filePath) : ctx;
2139
1880
  try {
2140
- const result = parse(normalized?.code ?? code, ctxForAstro);
2141
- if (normalized.needRemap) {
2142
- remap(result, normalized, code, ctxForAstro);
2143
- ctx.originalAST = ctxForAstro.originalAST;
2144
- }
2145
1881
  const templateResult = {
2146
- result,
1882
+ result: parse$1(code, ctx),
2147
1883
  context: ctx
2148
1884
  };
2149
1885
  lruCache.set(code, templateResult);
2150
1886
  return templateResult;
2151
1887
  } catch (e) {
2152
1888
  if (typeof e.pos === "number") {
2153
- const err = new ParseError(e.message, normalized?.remapIndex(e.pos), ctx);
1889
+ const err = new ParseError(e.message, e.pos, ctx);
2154
1890
  err.astroCompilerError = e;
2155
1891
  throw err;
2156
1892
  }
2157
1893
  throw e;
2158
1894
  }
2159
1895
  }
2160
- /** Remap */
2161
- function remap(result, normalized, originalCode, ctxForAstro) {
2162
- const remapDataMap = /* @__PURE__ */ new Map();
2163
- walk(result.ast, normalized.code, (node) => {
2164
- const start = normalized.remapIndex(node.position.start.offset);
2165
- let end, value;
2166
- if (node.position.end) {
2167
- end = normalized.remapIndex(node.position.end.offset);
2168
- if (node.position.start.offset === start && node.position.end.offset === end) return;
2169
- }
2170
- if (node.type === "text") value = originalCode.slice(start, normalized.remapIndex(getEndOffset(node, ctxForAstro)));
2171
- else if (node.type === "comment") value = originalCode.slice(start + 4, normalized.remapIndex(getEndOffset(node, ctxForAstro)) - 3);
2172
- else if (node.type === "attribute") {
2173
- if (node.kind !== "empty" && node.kind !== "shorthand" && node.kind !== "spread") {
2174
- let valueStart = normalized.remapIndex(calcAttributeValueStartOffset(node, ctxForAstro));
2175
- let valueEnd = normalized.remapIndex(calcAttributeEndOffset(node, ctxForAstro));
2176
- if (node.kind !== "quoted" || originalCode[valueStart] === "\"" || originalCode[valueStart] === "'") {
2177
- valueStart++;
2178
- valueEnd--;
2179
- }
2180
- value = originalCode.slice(valueStart, valueEnd);
2181
- }
2182
- }
2183
- remapDataMap.set(node, {
2184
- start,
2185
- end,
2186
- value
2187
- });
2188
- }, (_node) => {});
2189
- for (const [node, remapData] of remapDataMap) {
2190
- node.position.start.offset = remapData.start;
2191
- if (node.position.end) node.position.end.offset = remapData.end;
2192
- if (node.type === "text" || node.type === "comment" || node.type === "attribute" && node.kind !== "empty" && node.kind !== "shorthand" && node.kind !== "spread") node.value = remapData.value;
2193
- }
2194
- }
2195
1896
  //#endregion
2196
1897
  //#region src/context/resolve-parser/espree.ts
2197
1898
  let espreeCache = null;
@@ -2239,8 +1940,9 @@ function getParser(attrs, parser) {
2239
1940
  //#region src/context/parser-options.ts
2240
1941
  const TS_PARSER_NAMES = ["@typescript-eslint/parser", "typescript-eslint-parser-for-extra-files"];
2241
1942
  var ParserOptionsContext = class {
1943
+ parserOptions;
1944
+ state = {};
2242
1945
  constructor(options) {
2243
- this.state = {};
2244
1946
  const parserOptions = {
2245
1947
  ecmaVersion: 2020,
2246
1948
  sourceType: "module",
@@ -2397,11 +2099,8 @@ function parseTemplate(code) {
2397
2099
  const parsed = parseTemplate$1(code);
2398
2100
  return {
2399
2101
  result: parsed.result,
2400
- getEndOffset: (node) => getEndOffset(node, parsed.context),
2401
- calcAttributeValueStartOffset: (node) => calcAttributeValueStartOffset(node, parsed.context),
2402
- calcAttributeEndOffset: (node) => calcAttributeEndOffset(node, parsed.context),
2403
2102
  walk(parent, enter, leave) {
2404
- walk(parent, code, enter, leave || (() => {}));
2103
+ walk(parent, enter, leave);
2405
2104
  },
2406
2105
  getLocFromIndex: (index) => parsed.context.getLocFromIndex(index),
2407
2106
  getIndexFromLoc: (loc) => parsed.context.locs.getIndexFromLoc(loc)
@@ -2413,7 +2112,7 @@ var ast_exports = /* @__PURE__ */ __exportAll({});
2413
2112
  //#endregion
2414
2113
  //#region package.json
2415
2114
  var name = "astro-eslint-parser";
2416
- var version = "2.1.0";
2115
+ var version = "3.0.0";
2417
2116
  //#endregion
2418
2117
  //#region src/meta.ts
2419
2118
  var meta_exports = /* @__PURE__ */ __exportAll({