prettier-plugin-astro 1.0.0-beta.0 → 1.0.0-beta.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,4 +1,5 @@
1
1
  import { parse as parse$1 } from '@astrojs/compiler-rs';
2
+ import parseSrcset from '@prettier/parse-srcset';
2
3
  import { doc } from 'prettier';
3
4
  import { printers as printers$1 } from 'prettier/plugins/estree';
4
5
  import { SassFormatter } from 'sass-formatter';
@@ -53,6 +54,15 @@ function tagNameOf(node) {
53
54
  const name = node.openingElement?.name;
54
55
  return name?.type === 'JSXIdentifier' ? name.name : null;
55
56
  }
57
+ function jsxNameOf(name) {
58
+ if (name.type === 'JSXIdentifier')
59
+ return name.name;
60
+ if (name.type !== 'JSXMemberExpression')
61
+ return null;
62
+ const object = jsxNameOf(name.object);
63
+ const property = jsxNameOf(name.property);
64
+ return object !== null && property !== null ? `${object}.${property}` : null;
65
+ }
56
66
  const isComponentName = (name) => name === null || /^[A-Z]/.test(name) || name.includes('.');
57
67
  function attributesOf(node) {
58
68
  const opening = node.openingElement;
@@ -242,19 +252,20 @@ function isInlineDisplay(display) {
242
252
  return inlineLevel.has(display);
243
253
  }
244
254
 
255
+ const { group: group$3, ifBreak, indent: indent$3, join: join$1, line: line$2, softline: softline$3 } = doc.builders;
245
256
  function manualDedent(input) {
246
257
  let minTabSize = Infinity;
247
258
  let result = input;
248
259
  result = result.replace(/\r\n/g, '\n');
249
260
  let char = '';
250
- for (const line of result.split('\n')) {
251
- if (!line)
261
+ for (const row of result.split('\n')) {
262
+ if (!row)
252
263
  continue;
253
- if (line[0] && /^\S/.test(line[0])) {
264
+ if (row[0] && /^\S/.test(row[0])) {
254
265
  minTabSize = 0;
255
266
  break;
256
267
  }
257
- const match = /^(\s+)\S+/.exec(line);
268
+ const match = /^(\s+)\S+/.exec(row);
258
269
  if (match) {
259
270
  if (match[1] && !char)
260
271
  char = match[1][0];
@@ -271,11 +282,39 @@ function manualDedent(input) {
271
282
  result,
272
283
  };
273
284
  }
285
+ function printSrcset(value) {
286
+ const candidates = parseSrcset(decodeQuoteEntities(value));
287
+ const descriptorUnits = { width: 'w', height: 'h', density: 'x' };
288
+ const descriptorTypes = Object.keys(descriptorUnits).filter((type) => candidates.some((candidate) => candidate[type]));
289
+ if (descriptorTypes.length > 1)
290
+ throw new Error('Mixed descriptor in srcset is not supported');
291
+ const descriptorType = descriptorTypes[0];
292
+ const unit = descriptorType ? descriptorUnits[descriptorType] : '';
293
+ const urls = candidates.map((candidate) => candidate.source.value);
294
+ const widestUrl = Math.max(...urls.map((url) => url.length));
295
+ const descriptors = candidates.map((candidate) => descriptorType && candidate[descriptorType] ? String(candidate[descriptorType].value) : '');
296
+ const descriptorLeftLengths = descriptors.map((descriptor) => {
297
+ const decimal = descriptor.indexOf('.');
298
+ return decimal === -1 ? descriptor.length : decimal;
299
+ });
300
+ const widestDescriptorLeft = Math.max(...descriptorLeftLengths);
301
+ const printed = urls.map((url, index) => {
302
+ const parts = [url.replaceAll('"', '"')];
303
+ const descriptor = descriptors[index];
304
+ if (descriptor) {
305
+ const padding = widestUrl - url.length + 1 + widestDescriptorLeft - descriptorLeftLengths[index];
306
+ parts.push(ifBreak(' '.repeat(padding), ' '), descriptor + unit);
307
+ }
308
+ return parts;
309
+ });
310
+ return group$3([indent$3([softline$3, join$1([',', line$2], printed)]), softline$3]);
311
+ }
312
+ const decodeQuoteEntities = (text) => text.replaceAll(''', "'").replaceAll('"', '"');
274
313
  function printClassNames(value) {
275
314
  const lines = value.trim().split(/[\r\n]+/);
276
- const formattedLines = lines.map((line) => {
277
- const spaces = /^\s+/.exec(line);
278
- return (spaces ? spaces[0] : '') + line.trim().split(/\s+/).join(' ');
315
+ const formattedLines = lines.map((row) => {
316
+ const spaces = /^\s+/.exec(row);
317
+ return (spaces ? spaces[0] : '') + row.trim().split(/\s+/).join(' ');
279
318
  });
280
319
  return formattedLines.join('\n');
281
320
  }
@@ -284,11 +323,14 @@ const leadingWhitespace$1 = /^[\t\n\f\r ]+/;
284
323
  const trailingWhitespace$1 = /[\t\n\f\r ]+$/;
285
324
  const hasNewline = (run) => /[\n\r]/.test(run);
286
325
  const isText = (node) => node?.type === 'JSXText';
326
+ const isComment = (node) => node?.type === 'AstroComment';
287
327
  const rawTextOf = (node) => node.raw ?? '';
288
328
  const isWhitespaceOnly = (node) => isText(node) && rawTextOf(node).trim().length === 0;
289
329
  function opensRawSubtree(node) {
290
330
  if (node.type !== 'JSXElement')
291
331
  return false;
332
+ if (node.astroPreserveWhitespace)
333
+ return true;
292
334
  if (hasAttribute(node, 'is:raw'))
293
335
  return true;
294
336
  const tag = tagNameOf(node);
@@ -301,9 +343,62 @@ function displayOf(node) {
301
343
  const tag = tagNameOf(node);
302
344
  if (tag === null || isComponentName(tag) || tag.includes('-'))
303
345
  return 'inline';
346
+ if (node.astroSvg)
347
+ return tag === 'svg' ? 'inline-block' : node.astroSvgText ? 'inline' : 'block';
304
348
  return displayOfTag(tag);
305
349
  }
350
+ const isBlockBox = (node) => node !== null && node.type !== 'JSXText' && !isInlineDisplay(displayOf(node));
351
+ const swallowsEdgeWhitespace = (node) => {
352
+ const display = displayOf(node);
353
+ return display === 'inline-block' || !isInlineDisplay(display);
354
+ };
355
+ const breaksOwnChildren = new Set(['html', 'head', 'ul', 'ol', 'select']);
356
+ const breaksOwnContent = new Set(['body', 'script', 'style']);
357
+ function breaksChildren(node) {
358
+ const tag = node.type === 'JSXElement' ? tagNameOf(node) : null;
359
+ if (tag === null || isComponentName(tag))
360
+ return false;
361
+ if (breaksOwnChildren.has(tag))
362
+ return true;
363
+ const display = displayOfTag(tag);
364
+ return display.startsWith('table') && display !== 'table-cell';
365
+ }
366
+ const hasElementChild = (node) => childrenOf(node)?.some((child) => child.type === 'JSXElement') ?? false;
367
+ const runBefore = (node) => node && isText(node) ? (trailingWhitespace$1.exec(rawTextOf(node))?.[0] ?? '') : '';
368
+ const runAfter = (node) => node && isText(node) ? (leadingWhitespace$1.exec(rawTextOf(node))?.[0] ?? '') : '';
369
+ function sitsOnItsOwnLine(container, child) {
370
+ if (child === null || isText(child))
371
+ return false;
372
+ const children = childrenOf(container);
373
+ const index = children?.indexOf(child) ?? -1;
374
+ if (index === -1)
375
+ return false;
376
+ return hasNewline(runBefore(children[index - 1])) && hasNewline(runAfter(children[index + 1]));
377
+ }
378
+ const hasChildOnItsOwnLine = (node) => childrenOf(node)?.some((child) => sitsOnItsOwnLine(node, child)) ?? false;
379
+ function forcesBreak(node) {
380
+ if (breaksChildren(node))
381
+ return true;
382
+ const children = childrenOf(node);
383
+ if (children === null)
384
+ return false;
385
+ const tag = node.type === 'JSXElement' ? tagNameOf(node) : null;
386
+ if (tag !== null && breaksOwnContent.has(tag))
387
+ return true;
388
+ return children.some(hasElementChild) || hasChildOnItsOwnLine(node);
389
+ }
306
390
  const isBlankRun = (run) => /[\n\r][^\S\n\r]*[\n\r]/.test(run);
391
+ const isEmptySlot = (node) => node.type === 'JSXElement' && tagNameOf(node) === 'slot' && (childrenOf(node)?.length ?? 0) === 0;
392
+ function canRenderEmpty(container) {
393
+ const tag = container.type === 'JSXElement' ? tagNameOf(container) : null;
394
+ if (tag === null || isComponentName(tag))
395
+ return false;
396
+ const children = childrenOf(container);
397
+ if (children === null)
398
+ return false;
399
+ const content = children.filter((child) => !isWhitespaceOnly(child) && !isComment(child));
400
+ return (children.some((child) => isComment(child) || isEmptySlot(child)) && content.every(isEmptySlot));
401
+ }
307
402
  function sidesFor(boundary) {
308
403
  if (boundary.edge)
309
404
  return [null];
@@ -318,7 +413,7 @@ function sidesFor(boundary) {
318
413
  sides.push(boundary.next);
319
414
  return sides.length > 0 ? sides : null;
320
415
  }
321
- function blankContentIsFree(container, run, isRoot, settings) {
416
+ function blankContentIsFree(container, isRoot, settings) {
322
417
  const tag = container.type === 'JSXElement' ? tagNameOf(container) : null;
323
418
  if (tag === 'slot')
324
419
  return false;
@@ -352,16 +447,26 @@ function separatorFor(run, boundary, settings) {
352
447
  return run === '' ? 'none' : 'space';
353
448
  const free = isFree(internal) || (sides !== null && mayAlterRenderedWhitespace(internal));
354
449
  if (boundary.edge) {
450
+ if (settings.sensitivity !== 'ignore' && canRenderEmpty(boundary.container)) {
451
+ return run === '' ? 'none' : 'space';
452
+ }
355
453
  if (free)
356
454
  return 'soft';
357
455
  return run === '' ? 'none' : 'space';
358
456
  }
359
457
  if (isBlankRun(run))
360
458
  return 'blank';
361
- if (hasNewline(run))
459
+ if (settings.sensitivity === 'ignore')
460
+ return 'break';
461
+ if (hasNewline(run) &&
462
+ (sitsOnItsOwnLine(boundary.container, boundary.prev) ||
463
+ sitsOnItsOwnLine(boundary.container, boundary.next))) {
464
+ return 'break';
465
+ }
466
+ if (run !== '' && (isComment(boundary.prev) || isComment(boundary.next)))
362
467
  return 'break';
363
468
  if (free)
364
- return 'soft';
469
+ return isBlockBox(boundary.prev) || isBlockBox(boundary.next) ? 'break' : 'soft';
365
470
  return run === '' ? 'none' : 'space';
366
471
  }
367
472
  function normalizeWhitespace(template, options) {
@@ -455,6 +560,8 @@ function decide(run, boundary) {
455
560
  const { mode } = boundary.context;
456
561
  if (isSlotFallback(boundary))
457
562
  return 'space';
563
+ if (boundary.context.sensitivity !== 'ignore' && canRenderEmpty(boundary.container))
564
+ return 'keep';
458
565
  if (isFree(boundary))
459
566
  return 'drop';
460
567
  if (mayAlterRenderedWhitespace(boundary))
@@ -475,11 +582,11 @@ function isFree(boundary) {
475
582
  const tag = container.type === 'JSXElement' ? tagNameOf(container) : null;
476
583
  if (loneChild && tag !== null && isComponentName(tag))
477
584
  return true;
585
+ if (context.sensitivity !== 'strict' && breaksChildren(container))
586
+ return true;
478
587
  if (mode === 'html') {
479
588
  if (loneChild)
480
589
  return true;
481
- if (tag === 'head')
482
- return true;
483
590
  }
484
591
  return false;
485
592
  }
@@ -489,7 +596,7 @@ function mayAlterRenderedWhitespace(boundary) {
489
596
  return true;
490
597
  if (sensitivity === 'strict')
491
598
  return false;
492
- return boundary.sides.every((side) => !isInlineDisplay(displayOf(side ?? boundary.container)));
599
+ return boundary.sides.some((side) => side === null ? swallowsEdgeWhitespace(boundary.container) : isBlockBox(side));
493
600
  }
494
601
  function isSlotFallback(boundary) {
495
602
  return (boundary.container.type === 'JSXElement' &&
@@ -502,9 +609,10 @@ function parse(source, options) {
502
609
  const failure = diagnostics.find((diagnostic) => diagnostic.severity === 'error');
503
610
  if (failure)
504
611
  throw syntaxError(failure);
505
- stripParenthesizedExpressions(ast);
612
+ stripParentheses(ast);
506
613
  repairSpans(ast);
507
614
  markAttributes(ast, source);
615
+ markSvgNamespace(ast);
508
616
  applyPrettierIgnore(ast);
509
617
  const body = ast.body;
510
618
  const template = templateFragment(ast, body);
@@ -532,7 +640,7 @@ function syntaxError(diagnostic) {
532
640
  error.loc = { start: { line, column } };
533
641
  return error;
534
642
  }
535
- function stripParenthesizedExpressions(root) {
643
+ function stripParentheses(root) {
536
644
  walk(root, (node) => {
537
645
  for (const key of Object.keys(node)) {
538
646
  const value = node[key];
@@ -549,8 +657,13 @@ function stripParenthesizedExpressions(root) {
549
657
  }
550
658
  function unwrapParens(node) {
551
659
  let current = node;
552
- while (isNode(current) && current.type === 'ParenthesizedExpression') {
553
- current = current.expression;
660
+ while (isNode(current)) {
661
+ if (current.type === 'ParenthesizedExpression')
662
+ current = current.expression;
663
+ else if (current.type === 'TSParenthesizedType')
664
+ current = current.typeAnnotation;
665
+ else
666
+ break;
554
667
  }
555
668
  return current;
556
669
  }
@@ -567,28 +680,43 @@ function repairSpans(root) {
567
680
  }
568
681
  function markAttributes(root, source) {
569
682
  walk(root, (node) => {
570
- if (node.type !== 'JSXAttribute')
571
- return;
572
- const value = node.value;
573
- if (!value)
683
+ if (node.type !== 'JSXElement')
574
684
  return;
575
- if (value.type === 'Literal' && typeof value.value === 'string') {
576
- const name = node.name.name;
577
- const text = name === 'class' ? printClassNames(value.value) : value.value;
578
- if (value.raw === null || text !== value.value) {
579
- value.value = text;
580
- value.raw = `"${text.replaceAll('"', '"')}"`;
685
+ const tag = tagNameOf(node);
686
+ const htmlElement = tag !== null && !isComponentName(tag);
687
+ for (const attribute of attributesOf(node)) {
688
+ if (attribute.type !== 'JSXAttribute')
689
+ continue;
690
+ const name = String(attribute.name.name).toLowerCase();
691
+ if (htmlElement && name === 'style')
692
+ attribute.astroStyleAttribute = true;
693
+ if ((tag === 'img' || tag === 'source') && name === 'srcset') {
694
+ attribute.astroSrcsetAttribute = true;
581
695
  }
582
- return;
696
+ markAttribute(attribute, source);
583
697
  }
584
- if (value.type !== 'JSXExpressionContainer')
585
- return;
586
- if (source[node.start] === '{')
587
- node.astroShorthand = true;
588
- if (source[value.start] === '`')
589
- node.astroBacktick = true;
590
698
  });
591
699
  }
700
+ function markAttribute(node, source) {
701
+ const value = node.value;
702
+ if (!value)
703
+ return;
704
+ if (value.type === 'Literal' && typeof value.value === 'string') {
705
+ const name = node.name.name;
706
+ const text = name === 'class' ? printClassNames(value.value) : value.value;
707
+ if (value.raw === null || text !== value.value) {
708
+ value.value = text;
709
+ value.raw = `"${text.replaceAll('"', '"')}"`;
710
+ }
711
+ return;
712
+ }
713
+ if (value.type !== 'JSXExpressionContainer')
714
+ return;
715
+ if (source[node.start] === '{')
716
+ node.astroShorthand = true;
717
+ if (source[value.start] === '`')
718
+ node.astroBacktick = true;
719
+ }
592
720
  function applyPrettierIgnore(root) {
593
721
  walk(root, (node) => {
594
722
  const children = node.type === 'AstroRoot' ? node.body : childrenOf(node);
@@ -605,6 +733,28 @@ function applyPrettierIgnore(root) {
605
733
  }
606
734
  });
607
735
  }
736
+ const svgTextElements = new Set(['text', 'textPath', 'tspan']);
737
+ function markSvgNamespace(root) {
738
+ const mark = (node, inText = false, inheritedPreserve = false) => {
739
+ node.astroSvg = true;
740
+ const textContent = inText || svgTextElements.has(tagNameOf(node) ?? '');
741
+ const whitespace = attributeStringValue(node, 'xml:space');
742
+ const preserve = whitespace === 'preserve' || (inheritedPreserve && whitespace !== 'default');
743
+ if (textContent)
744
+ node.astroSvgText = true;
745
+ if (textContent && preserve)
746
+ node.astroPreserveWhitespace = true;
747
+ for (const child of childrenOf(node) ?? []) {
748
+ if (child.type !== 'JSXElement' || tagNameOf(child) === 'foreignObject')
749
+ continue;
750
+ mark(child, textContent, preserve);
751
+ }
752
+ };
753
+ walk(root, (node) => {
754
+ if (node.type === 'JSXElement' && tagNameOf(node) === 'svg')
755
+ mark(node);
756
+ });
757
+ }
608
758
  function templateFragment(root, body) {
609
759
  const start = body[0]?.start ?? root.end;
610
760
  const end = body.at(-1)?.end ?? root.end;
@@ -673,8 +823,7 @@ function resolveBlankContainers(template, settings) {
673
823
  const blank = children.every((child) => child.type === 'JSXText' && String(child.raw ?? '').trim() === '');
674
824
  if (!blank)
675
825
  return;
676
- const run = children.map((child) => String(child.raw ?? '')).join('');
677
- const free = blankContentIsFree(node, run, node.astroRoot === true, settings);
826
+ const free = blankContentIsFree(node, node.astroRoot === true, settings);
678
827
  children.length = 0;
679
828
  if (!free) {
680
829
  children.push({ type: 'JSXText', start: node.start, end: node.start, raw: ' ', value: ' ' });
@@ -713,32 +862,38 @@ function printableComments(root) {
713
862
 
714
863
  const estree = printers$1.estree;
715
864
 
716
- const { fill, group: group$1, hardline: hardline$2, indent: indent$1, line, softline } = doc.builders;
865
+ const { fill, group: group$2, hardline: hardline$2, indent: indent$2, line: line$1, softline: softline$2 } = doc.builders;
717
866
  const leadingWhitespace = /^[\t\n\f\r ]+/;
718
867
  const trailingWhitespace = /[\t\n\f\r ]+$/;
719
868
  const whitespaceRun = /[\t\n\f\r ]+/;
869
+ const lends = 'lends-closing-bracket';
870
+ const withholdsClosingBracket = (node) => node.type === 'JSXElement' &&
871
+ !node.astroIgnored &&
872
+ Boolean(node.closingElement) &&
873
+ Boolean(node.astroChildren) &&
874
+ !opensRawSubtree(node);
720
875
  function docFor(separator) {
721
876
  switch (separator) {
722
877
  case 'none':
723
878
  return null;
724
879
  case 'soft':
725
- return softline;
880
+ return softline$2;
726
881
  case 'space':
727
- return line;
882
+ return line$1;
728
883
  case 'break':
729
884
  return hardline$2;
730
885
  case 'blank':
731
886
  return [hardline$2, hardline$2];
732
887
  }
733
888
  }
734
- function collect(children, docs) {
889
+ function collect(children) {
735
890
  const items = [];
736
891
  const runs = [];
737
892
  let pending = '';
738
893
  for (const [index, child] of children.entries()) {
739
894
  if (child.type !== 'JSXText') {
740
895
  runs.push(pending);
741
- items.push({ node: child, words: null, doc: docs[index] });
896
+ items.push({ node: child, index, words: null, doc: '' });
742
897
  pending = '';
743
898
  continue;
744
899
  }
@@ -752,6 +907,7 @@ function collect(children, docs) {
752
907
  runs.push(pending + lead);
753
908
  items.push({
754
909
  node: child,
910
+ index,
755
911
  words: raw.slice(lead.length, raw.length - trail.length).split(whitespaceRun),
756
912
  doc: '',
757
913
  });
@@ -760,13 +916,11 @@ function collect(children, docs) {
760
916
  runs.push(pending);
761
917
  return { items, runs };
762
918
  }
763
- function printChildren(path, options, print) {
919
+ function printChildren(path, options, print, childrenOptions = {}) {
920
+ const { mode, suffix } = childrenOptions;
764
921
  const container = path.node;
765
- const docs = [];
766
- path.each((child) => {
767
- docs.push(child.node.type === 'JSXText' ? '' : print());
768
- }, 'astroChildren');
769
- const { items, runs } = collect(container.astroChildren, docs);
922
+ const children = container.astroChildren;
923
+ const { items, runs } = collect(children);
770
924
  if (items.length === 0)
771
925
  return runs[0] === '' ? '' : ' ';
772
926
  const isRoot = container.astroRoot === true;
@@ -782,6 +936,28 @@ function printChildren(path, options, print) {
782
936
  loneChild: false,
783
937
  edge,
784
938
  }, settings));
939
+ const lenders = new Set();
940
+ const lending = new Set();
941
+ for (let position = 1; position < items.length; position++) {
942
+ if (separatorAt(position, false) !== null)
943
+ continue;
944
+ if (!withholdsClosingBracket(items[position - 1].node))
945
+ continue;
946
+ lenders.add(position);
947
+ lending.add(items[position - 1].index);
948
+ }
949
+ if (mode === 'fill-lending')
950
+ lending.add(items.at(-1).index);
951
+ const printed = new Map();
952
+ path.each((child, index) => {
953
+ if (child.node.type === 'JSXText')
954
+ return;
955
+ printed.set(index, print(undefined, lending.has(index) ? lends : undefined));
956
+ }, 'astroChildren');
957
+ for (const item of items) {
958
+ if (item.words === null)
959
+ item.doc = printed.get(item.index) ?? '';
960
+ }
785
961
  const parts = [''];
786
962
  const append = (content) => parts.push([parts.pop(), content]);
787
963
  const separate = (separator) => {
@@ -789,26 +965,41 @@ function printChildren(path, options, print) {
789
965
  parts.push(separator, '');
790
966
  };
791
967
  for (const [position, item] of items.entries()) {
792
- if (position > 0)
793
- separate(separatorAt(position, false));
968
+ if (position > 0) {
969
+ if (lenders.has(position)) {
970
+ parts.push(softline$2, '');
971
+ append('>');
972
+ }
973
+ else
974
+ separate(separatorAt(position, false));
975
+ }
794
976
  if (item.words === null) {
795
977
  append(item.doc);
796
978
  continue;
797
979
  }
798
980
  for (const [wordPosition, word] of item.words.entries()) {
799
981
  if (wordPosition > 0)
800
- separate(line);
982
+ separate(line$1);
801
983
  append(word);
802
984
  }
803
985
  }
986
+ if (suffix)
987
+ append(suffix);
804
988
  const body = fill(parts);
805
- if (isRoot)
989
+ if (isRoot || mode === 'fill' || mode === 'fill-lending')
806
990
  return body;
807
- return group$1([indent$1([separatorAt(0, true) ?? '', body]), separatorAt(items.length, true) ?? '']);
991
+ const content = [
992
+ indent$2([separatorAt(0, true) ?? '', body]),
993
+ separatorAt(items.length, true) ?? '',
994
+ ];
995
+ if (mode === 'loose')
996
+ return content;
997
+ return group$2(content, { shouldBreak: forcesBreak(container) });
808
998
  }
809
999
 
810
- const { group, hardline: hardline$1, indent, join } = doc.builders;
811
- const { replaceEndOfLine: replaceEndOfLine$1 } = doc.utils;
1000
+ const { group: group$1, hardline: hardline$1, indent: indent$1, join, softline: softline$1 } = doc.builders;
1001
+ const { mapDoc, replaceEndOfLine: replaceEndOfLine$1 } = doc.utils;
1002
+ const escapeQuotes = (value) => mapDoc(value, (part) => (typeof part === 'string' ? part.replaceAll('"', '&quot;') : part));
812
1003
  const styleParsers = {
813
1004
  css: 'css',
814
1005
  scss: 'scss',
@@ -846,6 +1037,20 @@ function inferParserByTypeAttribute(type) {
846
1037
  return 'babel-ts';
847
1038
  }
848
1039
  }
1040
+ function inferScriptParser(node) {
1041
+ const processed = attributesOf(node).every((attribute) => attribute.type === 'JSXAttribute' && attribute.name.name === 'src');
1042
+ if (processed)
1043
+ return 'babel-ts';
1044
+ return inferParserByTypeAttribute(attributeStringValue(node, 'type'));
1045
+ }
1046
+ function styleAttributeValue(node) {
1047
+ if (!node.astroStyleAttribute)
1048
+ return null;
1049
+ const value = node.value;
1050
+ if (value?.type !== 'Literal' || typeof value.value !== 'string')
1051
+ return null;
1052
+ return value.value.trim() === '' ? null : value.value;
1053
+ }
849
1054
  function contentOf(node, options) {
850
1055
  const start = node.openingElement.end;
851
1056
  const end = node.closingElement?.start ?? node.end;
@@ -854,7 +1059,7 @@ function contentOf(node, options) {
854
1059
  function wrapContent(print, content, isEmpty) {
855
1060
  return [
856
1061
  print('openingElement'),
857
- indent([isEmpty ? '' : hardline$1, content]),
1062
+ indent$1([hardline$1, content]),
858
1063
  isEmpty ? '' : hardline$1,
859
1064
  print('closingElement'),
860
1065
  ];
@@ -887,6 +1092,20 @@ function embed(path, options) {
887
1092
  '---',
888
1093
  ];
889
1094
  }
1095
+ if (node.type === 'JSXAttribute' && options.astroCompressHTML !== 'jsx') {
1096
+ const style = styleAttributeValue(node);
1097
+ if (style !== null) {
1098
+ return async (textToDoc) => {
1099
+ const declarations = await textToDoc(decodeQuoteEntities(style), {
1100
+ ...options,
1101
+ parser: 'css',
1102
+ __isHTMLStyleAttribute: true,
1103
+ });
1104
+ const value = escapeQuotes(declarations);
1105
+ return ['style="', group$1([indent$1([softline$1, value]), softline$1]), '"'];
1106
+ };
1107
+ }
1108
+ }
890
1109
  if (node.type !== 'JSXElement')
891
1110
  return estree.embed(path, options);
892
1111
  const tag = tagNameOf(node);
@@ -897,7 +1116,7 @@ function embed(path, options) {
897
1116
  if (tag === 'script') {
898
1117
  if (isEmpty)
899
1118
  return estree.embed(path, options);
900
- const parser = inferParserByTypeAttribute(attributeStringValue(node, 'type'));
1119
+ const parser = inferScriptParser(node);
901
1120
  return async (textToDoc, print) => wrapContent(print, await surfacingErrors(textToDoc, source, { ...options, parser }), false);
902
1121
  }
903
1122
  if (tag === 'style') {
@@ -922,10 +1141,10 @@ function embed(path, options) {
922
1141
  return estree.embed(path, options);
923
1142
  }
924
1143
  function printVerbatim(source) {
925
- return (_textToDoc, print) => group([print('openingElement'), replaceEndOfLine$1(source), print('closingElement')]);
1144
+ return (_textToDoc, print) => group$1([print('openingElement'), replaceEndOfLine$1(source), print('closingElement')]);
926
1145
  }
927
1146
 
928
- const { hardline } = doc.builders;
1147
+ const { group, hardline, indent, line, softline } = doc.builders;
929
1148
  const { replaceEndOfLine } = doc.utils;
930
1149
  function printDoctype(value) {
931
1150
  const trimmed = value.trim();
@@ -978,6 +1197,18 @@ function printAttribute(path, options, print) {
978
1197
  return ['{', print(['value', 'expression']), '}'];
979
1198
  if (node.astroBacktick)
980
1199
  return [name, '=', print(['value', 'expression'])];
1200
+ if (options.astroCompressHTML !== 'jsx' &&
1201
+ node.astroSrcsetAttribute &&
1202
+ value?.type === 'Literal' &&
1203
+ typeof value.value === 'string' &&
1204
+ value.value.trim() !== '') {
1205
+ try {
1206
+ return ['srcset="', printSrcset(value.value), '"'];
1207
+ }
1208
+ catch {
1209
+ return ['srcset="', value.value.replaceAll('"', '&quot;'), '"'];
1210
+ }
1211
+ }
981
1212
  const expression = value?.type === 'JSXExpressionContainer' ? value.expression : null;
982
1213
  if (options.astroAllowShorthand &&
983
1214
  expression?.type === 'Identifier' &&
@@ -986,15 +1217,82 @@ function printAttribute(path, options, print) {
986
1217
  }
987
1218
  return null;
988
1219
  }
1220
+ function printBreakableOpeningTag(path, options, print) {
1221
+ const node = path.node;
1222
+ const attributes = node.attributes;
1223
+ if (attributes.length !== 1)
1224
+ return null;
1225
+ const value = attributes[0].value;
1226
+ if (value?.type !== 'Literal' || typeof value.value !== 'string')
1227
+ return null;
1228
+ if (value.value.includes('\n'))
1229
+ return null;
1230
+ const end = node.selfClosing
1231
+ ? options.bracketSameLine
1232
+ ? [' />']
1233
+ : [line, '/>']
1234
+ : options.bracketSameLine
1235
+ ? ['>']
1236
+ : [softline, '>'];
1237
+ return group(['<', print('name'), indent([line, print(['attributes', 0])]), ...end]);
1238
+ }
1239
+ function printWithDanglingBrackets(path, print, lending) {
1240
+ const node = path.node;
1241
+ const children = node.astroChildren;
1242
+ if (!children?.length || !node.closingElement)
1243
+ return null;
1244
+ if (swallowsEdgeWhitespace(node))
1245
+ return null;
1246
+ if (startsWithSpace(children[0]) || endsWithSpace(children.at(-1)))
1247
+ return null;
1248
+ const opening = print('openingElement');
1249
+ if (opening.type !== 'group' || !Array.isArray(opening.contents))
1250
+ return null;
1251
+ const contents = opening.contents.filter((part) => part !== '');
1252
+ if (contents.at(-1) !== '>')
1253
+ return null;
1254
+ const withoutBracket = contents.slice(0, -1);
1255
+ if (withoutBracket.at(-1)?.type === 'line')
1256
+ withoutBracket.pop();
1257
+ const tag = jsxNameOf(node.closingElement.name);
1258
+ if (tag === null)
1259
+ return null;
1260
+ const head = { ...opening, contents: withoutBracket };
1261
+ const shouldBreak = forcesBreak(node);
1262
+ if (isSelfClosing(children.at(-1)) && !children.at(-1).astroIgnored) {
1263
+ const lentBody = print(['children', 0], { mode: 'fill-lending' });
1264
+ return group([head, indent([softline, '>', lentBody]), line, '/>', '</', tag, lending ? '' : '>'], { shouldBreak });
1265
+ }
1266
+ const body = print(['children', 0], { mode: 'fill', suffix: ['</', tag] });
1267
+ return group([head, indent([softline, '>', body]), lending ? '' : [softline, '>']], {
1268
+ shouldBreak,
1269
+ });
1270
+ }
1271
+ const isSelfClosing = (node) => node.type === 'JSXElement' && !node.closingElement;
1272
+ function withoutSelfClosingMarker(printed) {
1273
+ if (Array.isArray(printed)) {
1274
+ return printed.at(-1) === ' />' ? printed.slice(0, -1) : printed;
1275
+ }
1276
+ const tag = printed;
1277
+ if (tag.type !== 'group' || !Array.isArray(tag.contents))
1278
+ return printed;
1279
+ if (tag.contents.at(-1) === ' />')
1280
+ return { ...tag, contents: tag.contents.slice(0, -1) };
1281
+ if (tag.contents.at(-1) !== '/>')
1282
+ return printed;
1283
+ return { ...tag, contents: tag.contents.slice(0, -2) };
1284
+ }
1285
+ const startsWithSpace = (child) => child.type === 'JSXText' && /^[\t\n\f\r ]/.test(String(child.raw ?? ''));
1286
+ const endsWithSpace = (child) => child.type === 'JSXText' && /[\t\n\f\r ]$/.test(String(child.raw ?? ''));
989
1287
  function unwrapFragmentIndent(printed) {
990
- const group = printed;
991
- if (group.type !== 'group')
1288
+ const fragment = printed;
1289
+ if (fragment.type !== 'group')
992
1290
  return printed;
993
- if (group.expandedStates) {
994
- const states = group.expandedStates.map(unwrapFragmentIndent);
995
- return { ...group, contents: states[0], expandedStates: states };
1291
+ if (fragment.expandedStates) {
1292
+ const states = fragment.expandedStates.map(unwrapFragmentIndent);
1293
+ return { ...fragment, contents: states[0], expandedStates: states };
996
1294
  }
997
- const parts = group.contents;
1295
+ const parts = fragment.contents;
998
1296
  if (!Array.isArray(parts) || parts.length !== 4)
999
1297
  return printed;
1000
1298
  const indented = parts[1];
@@ -1013,8 +1311,9 @@ const printer = {
1013
1311
  },
1014
1312
  print(path, options, print, args) {
1015
1313
  const node = path.node;
1016
- if (node[ownChildren])
1017
- return path.callParent(() => printChildren(path, options, print));
1314
+ if (node[ownChildren]) {
1315
+ return path.callParent(() => printChildren(path, options, print, args ?? {}));
1316
+ }
1018
1317
  if (node[synthetic])
1019
1318
  return '';
1020
1319
  if (node.astroIgnored) {
@@ -1027,6 +1326,25 @@ const printer = {
1027
1326
  if (attribute)
1028
1327
  return attribute;
1029
1328
  }
1329
+ if (node.type === 'JSXOpeningElement' && options.astroCompressHTML !== 'jsx') {
1330
+ const opening = printBreakableOpeningTag(path, options, print);
1331
+ if (opening)
1332
+ return opening;
1333
+ }
1334
+ if (node.type === 'JSXElement' && args === lends && isSelfClosing(node)) {
1335
+ return withoutSelfClosingMarker(print('openingElement'));
1336
+ }
1337
+ if (node.type === 'JSXElement' && node.astroChildren && !opensRawSubtree(node)) {
1338
+ const dangling = printWithDanglingBrackets(path, print, args === lends);
1339
+ if (dangling)
1340
+ return dangling;
1341
+ const tag = jsxNameOf(node.closingElement.name);
1342
+ return group([
1343
+ print('openingElement'),
1344
+ print(['children', 0], { mode: 'loose' }),
1345
+ args === lends && tag !== null ? ['</', tag] : print('closingElement'),
1346
+ ], { shouldBreak: forcesBreak(node) });
1347
+ }
1030
1348
  if (node.type === 'JSXElement' &&
1031
1349
  node.closingElement &&
1032
1350
  node.children.length > 0 &&