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