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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
- import { parse } from '@astrojs/compiler/sync';
2
- import * as prettierPluginBabel from 'prettier/plugins/babel';
3
- import 'prettier';
4
- import { serialize } from '@astrojs/compiler/utils';
5
- import _doc from 'prettier/doc';
6
- import { Buffer } from 'node:buffer';
1
+ import { parse as parse$1 } from '@astrojs/compiler-rs';
2
+ import { doc } from 'prettier';
3
+ import { printers as printers$1 } from 'prettier/plugins/estree';
7
4
  import { SassFormatter } from 'sass-formatter';
8
5
 
9
6
  const options = {
@@ -19,9 +16,114 @@ const options = {
19
16
  default: false,
20
17
  description: 'Skips the formatting of the frontmatter.',
21
18
  },
19
+ astroCompressHTML: {
20
+ category: 'Astro',
21
+ type: 'choice',
22
+ default: 'jsx',
23
+ description: "Mirror of Astro's compressHTML config. Tells the formatter which whitespace the compiler will collapse.",
24
+ choices: [
25
+ {
26
+ value: 'jsx',
27
+ description: "Astro's default since 7.0.0. Whitespace runs containing a newline are dropped; same-line spaces are content.",
28
+ },
29
+ {
30
+ value: 'html',
31
+ description: 'Browser HTML whitespace rules. Equivalent to compressHTML: true.',
32
+ },
33
+ { value: 'none', description: 'No collapsing. Equivalent to compressHTML: false.' },
34
+ { value: true, redirect: 'html' },
35
+ { value: false, redirect: 'none' },
36
+ ],
37
+ },
22
38
  };
23
39
 
24
- const selfClosingTags = [
40
+ const synthetic = Symbol.for('prettier-plugin-astro.synthetic');
41
+ const ownChildren = Symbol.for('prettier-plugin-astro.ownChildren');
42
+ const astroVisitorKeys = {
43
+ AstroRoot: ['frontmatter', 'template'],
44
+ AstroFrontmatter: ['program'],
45
+ AstroScript: ['program'],
46
+ AstroDoctype: [],
47
+ AstroComment: [],
48
+ };
49
+ const isNode = (value) => typeof value === 'object' && value !== null && typeof value.type === 'string';
50
+ function tagNameOf(node) {
51
+ if (node.type !== 'JSXElement')
52
+ return null;
53
+ const name = node.openingElement?.name;
54
+ return name?.type === 'JSXIdentifier' ? name.name : null;
55
+ }
56
+ const isComponentName = (name) => name === null || /^[A-Z]/.test(name) || name.includes('.');
57
+ function attributesOf(node) {
58
+ const opening = node.openingElement;
59
+ return opening?.attributes ?? [];
60
+ }
61
+ function attributeNamed(node, name) {
62
+ return attributesOf(node).find((attribute) => attribute.type === 'JSXAttribute' && attribute.name.name === name);
63
+ }
64
+ function attributeStringValue(node, name) {
65
+ const value = attributeNamed(node, name)?.value;
66
+ return value?.type === 'Literal' && typeof value.value === 'string' ? value.value : null;
67
+ }
68
+ const hasAttribute = (node, name) => attributeNamed(node, name) !== undefined;
69
+ const hasSetDirective = (node) => hasAttribute(node, 'set:html') || hasAttribute(node, 'set:text');
70
+ function childrenOf(node) {
71
+ if (node.type === 'JSXElement' || node.type === 'JSXFragment') {
72
+ return node.astroChildren ?? node.children;
73
+ }
74
+ return null;
75
+ }
76
+ function takeOverChildren(node) {
77
+ const children = node.children;
78
+ if (children.length === 0)
79
+ return;
80
+ node.astroChildren = children;
81
+ node.children = [
82
+ {
83
+ type: 'JSXExpressionContainer',
84
+ start: node.start,
85
+ end: node.end,
86
+ [ownChildren]: true,
87
+ expression: {
88
+ type: 'TemplateLiteral',
89
+ start: node.start,
90
+ end: node.end,
91
+ quasis: [],
92
+ expressions: [],
93
+ },
94
+ },
95
+ ];
96
+ }
97
+ function walk(node, visit) {
98
+ if (Array.isArray(node)) {
99
+ for (const item of node)
100
+ walk(item, visit);
101
+ return;
102
+ }
103
+ if (!isNode(node))
104
+ return;
105
+ visit(node);
106
+ for (const key of Object.keys(node)) {
107
+ if (key !== 'type')
108
+ walk(node[key], visit);
109
+ }
110
+ }
111
+
112
+ const rawTextElements = new Set([
113
+ 'pre',
114
+ 'listing',
115
+ 'iframe',
116
+ 'noembed',
117
+ 'noframes',
118
+ 'math',
119
+ 'plaintext',
120
+ 'script',
121
+ 'style',
122
+ 'textarea',
123
+ 'title',
124
+ 'xmp',
125
+ ]);
126
+ const voidElements = new Set([
25
127
  'area',
26
128
  'base',
27
129
  'basefont',
@@ -42,267 +144,688 @@ const selfClosingTags = [
42
144
  'meta',
43
145
  'nextid',
44
146
  'param',
45
- 'slot',
46
147
  'source',
47
148
  'track',
48
149
  'wbr',
49
- ];
50
- const blockElements = [
51
- 'address',
52
- 'article',
53
- 'aside',
54
- 'blockquote',
55
- 'details',
56
- 'dialog',
57
- 'dd',
58
- 'div',
59
- 'dl',
60
- 'dt',
61
- 'fieldset',
62
- 'figcaption',
63
- 'figure',
64
- 'footer',
65
- 'form',
66
- 'h1',
67
- 'h2',
68
- 'h3',
69
- 'h4',
70
- 'h5',
71
- 'h6',
72
- 'header',
73
- 'hgroup',
74
- 'hr',
75
- 'li',
76
- 'main',
77
- 'nav',
78
- 'ol',
79
- 'p',
80
- 'pre',
81
- 'section',
82
- 'table',
83
- 'ul',
84
- 'title',
85
- 'html',
86
- ];
87
- const formattableAttributes = [];
88
-
89
- const openingBracketReplace = '_Pé';
90
- const closingBracketReplace = 'èP_';
91
- const atSignReplace = 'ΩP_';
92
- const dotReplace = 'ωP_';
93
- const interrogationReplace = 'ΔP_';
94
- function isInlineElement(path, opts, node) {
95
- return node && isTagLikeNode(node) && !isBlockElement(node, opts) && !isPreTagContent(path);
96
- }
97
- function isBlockElement(node, opts) {
98
- return (node &&
99
- node.type === 'element' &&
100
- opts.htmlWhitespaceSensitivity !== 'strict' &&
101
- (opts.htmlWhitespaceSensitivity === 'ignore' || blockElements.includes(node.name)));
102
- }
103
- function isIgnoreDirective(node) {
104
- return node.type === 'comment' && node.value.trim() === 'prettier-ignore';
105
- }
106
- function printRaw(node, stripLeadingAndTrailingNewline = false) {
107
- if (!isNodeWithChildren(node)) {
108
- return '';
109
- }
110
- if (node.children.length === 0) {
111
- return '';
112
- }
113
- let raw = node.children.reduce((prev, curr) => prev + serialize(curr), '');
114
- if (!stripLeadingAndTrailingNewline) {
115
- return raw;
116
- }
117
- if (startsWithLinebreak(raw)) {
118
- raw = raw.substring(raw.indexOf('\n') + 1);
119
- }
120
- if (endsWithLinebreak(raw)) {
121
- raw = raw.substring(0, raw.lastIndexOf('\n'));
122
- if (raw.charAt(raw.length - 1) === '\r') {
123
- raw = raw.substring(0, raw.length - 1);
124
- }
125
- }
126
- return raw;
127
- }
128
- function isNodeWithChildren(node) {
129
- return node && 'children' in node && Array.isArray(node.children);
130
- }
131
- const isEmptyTextNode = (node) => {
132
- return !!node && node.type === 'text' && getUnencodedText(node).trim() === '';
150
+ ]);
151
+ const cssDisplay = {
152
+ area: 'none',
153
+ base: 'none',
154
+ basefont: 'none',
155
+ datalist: 'none',
156
+ head: 'none',
157
+ link: 'none',
158
+ meta: 'none',
159
+ noembed: 'none',
160
+ noframes: 'none',
161
+ param: 'block',
162
+ rp: 'none',
163
+ script: 'block',
164
+ style: 'none',
165
+ template: 'inline',
166
+ title: 'none',
167
+ html: 'block',
168
+ body: 'block',
169
+ address: 'block',
170
+ blockquote: 'block',
171
+ center: 'block',
172
+ dialog: 'block',
173
+ div: 'block',
174
+ figure: 'block',
175
+ figcaption: 'block',
176
+ footer: 'block',
177
+ form: 'block',
178
+ header: 'block',
179
+ hr: 'block',
180
+ legend: 'block',
181
+ listing: 'block',
182
+ main: 'block',
183
+ p: 'block',
184
+ plaintext: 'block',
185
+ pre: 'block',
186
+ search: 'block',
187
+ xmp: 'block',
188
+ slot: 'contents',
189
+ ruby: 'ruby',
190
+ rt: 'ruby-text',
191
+ article: 'block',
192
+ aside: 'block',
193
+ h1: 'block',
194
+ h2: 'block',
195
+ h3: 'block',
196
+ h4: 'block',
197
+ h5: 'block',
198
+ h6: 'block',
199
+ hgroup: 'block',
200
+ nav: 'block',
201
+ section: 'block',
202
+ dir: 'block',
203
+ dd: 'block',
204
+ dl: 'block',
205
+ dt: 'block',
206
+ menu: 'block',
207
+ ol: 'block',
208
+ ul: 'block',
209
+ li: 'list-item',
210
+ table: 'table',
211
+ caption: 'table-caption',
212
+ colgroup: 'table-column-group',
213
+ col: 'table-column',
214
+ thead: 'table-header-group',
215
+ tbody: 'table-row-group',
216
+ tfoot: 'table-footer-group',
217
+ tr: 'table-row',
218
+ td: 'table-cell',
219
+ th: 'table-cell',
220
+ input: 'inline-block',
221
+ button: 'inline-block',
222
+ fieldset: 'block',
223
+ details: 'block',
224
+ summary: 'block',
225
+ marquee: 'inline-block',
226
+ source: 'block',
227
+ track: 'block',
228
+ meter: 'inline-block',
229
+ progress: 'inline-block',
230
+ object: 'inline-block',
231
+ video: 'inline-block',
232
+ audio: 'inline-block',
233
+ select: 'inline-block',
234
+ option: 'block',
235
+ optgroup: 'block',
133
236
  };
134
- function getUnencodedText(node) {
135
- return node.value;
136
- }
137
- function isTextNodeStartingWithLinebreak(node, nrLines = 1) {
138
- return startsWithLinebreak(getUnencodedText(node), nrLines);
237
+ const inlineLevel = new Set(['inline', 'inline-block', 'ruby', 'ruby-text', 'contents']);
238
+ function displayOfTag(tag) {
239
+ return cssDisplay[tag] ?? 'inline';
139
240
  }
140
- function startsWithLinebreak(text, nrLines = 1) {
141
- return new RegExp(`^([\\t\\f\\r ]*\\n){${nrLines}}`).test(text);
241
+ function isInlineDisplay(display) {
242
+ return inlineLevel.has(display);
142
243
  }
143
- function endsWithLinebreak(text, nrLines = 1) {
144
- return new RegExp(`(\\n[\\t\\f\\r ]*){${nrLines}}$`).test(text);
244
+
245
+ function manualDedent(input) {
246
+ let minTabSize = Infinity;
247
+ let result = input;
248
+ result = result.replace(/\r\n/g, '\n');
249
+ let char = '';
250
+ for (const line of result.split('\n')) {
251
+ if (!line)
252
+ continue;
253
+ if (line[0] && /^\S/.test(line[0])) {
254
+ minTabSize = 0;
255
+ break;
256
+ }
257
+ const match = /^(\s+)\S+/.exec(line);
258
+ if (match) {
259
+ if (match[1] && !char)
260
+ char = match[1][0];
261
+ if (match[1].length < minTabSize)
262
+ minTabSize = match[1].length;
263
+ }
264
+ }
265
+ if (minTabSize > 0 && Number.isFinite(minTabSize)) {
266
+ result = result.replace(new RegExp(`^${new Array(minTabSize + 1).join(char)}`, 'gm'), '');
267
+ }
268
+ return {
269
+ tabSize: minTabSize === Infinity ? 0 : minTabSize,
270
+ char,
271
+ result,
272
+ };
145
273
  }
146
- function isTextNodeStartingWithWhitespace(node) {
147
- return isTextNode(node) && /^\s/.test(getUnencodedText(node));
274
+ function printClassNames(value) {
275
+ 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(' ');
279
+ });
280
+ return formattedLines.join('\n');
148
281
  }
149
- function endsWithWhitespace(text) {
150
- return /\s$/.test(text);
282
+
283
+ const leadingWhitespace$1 = /^[\t\n\f\r ]+/;
284
+ const trailingWhitespace$1 = /[\t\n\f\r ]+$/;
285
+ const hasNewline = (run) => /[\n\r]/.test(run);
286
+ const isText = (node) => node?.type === 'JSXText';
287
+ const rawTextOf = (node) => node.raw ?? '';
288
+ const isWhitespaceOnly = (node) => isText(node) && rawTextOf(node).trim().length === 0;
289
+ function opensRawSubtree(node) {
290
+ if (node.type !== 'JSXElement')
291
+ return false;
292
+ if (hasAttribute(node, 'is:raw'))
293
+ return true;
294
+ const tag = tagNameOf(node);
295
+ return tag !== null && !isComponentName(tag) && rawTextElements.has(tag);
151
296
  }
152
- function isTextNodeEndingWithWhitespace(node) {
153
- return isTextNode(node) && endsWithWhitespace(getUnencodedText(node));
297
+ const isExtractedStyle = (node) => node !== null && tagNameOf(node) === 'style' && !hasAttribute(node, 'is:inline');
298
+ function displayOf(node) {
299
+ if (node.type !== 'JSXElement')
300
+ return 'inline';
301
+ const tag = tagNameOf(node);
302
+ if (tag === null || isComponentName(tag) || tag.includes('-'))
303
+ return 'inline';
304
+ return displayOfTag(tag);
154
305
  }
155
- function hasSetDirectives(node) {
156
- const attributes = Array.from(node.attributes, (attr) => attr.name);
157
- return attributes.some((attr) => ['set:html', 'set:text'].includes(attr));
306
+ const isBlankRun = (run) => /[\n\r][^\S\n\r]*[\n\r]/.test(run);
307
+ function sidesFor(boundary) {
308
+ if (boundary.edge)
309
+ return [null];
310
+ const sides = [];
311
+ if (boundary.prev === null)
312
+ sides.push(null);
313
+ else if (boundary.prev.type !== 'JSXText')
314
+ sides.push(boundary.prev);
315
+ if (boundary.next === null)
316
+ sides.push(null);
317
+ else if (boundary.next.type !== 'JSXText')
318
+ sides.push(boundary.next);
319
+ return sides.length > 0 ? sides : null;
158
320
  }
159
- function shouldHugStart(node, opts) {
160
- if (isBlockElement(node, opts)) {
321
+ function blankContentIsFree(container, run, isRoot, settings) {
322
+ const tag = container.type === 'JSXElement' ? tagNameOf(container) : null;
323
+ if (tag === 'slot')
161
324
  return false;
162
- }
163
- if (!isNodeWithChildren(node)) {
325
+ if (tag !== null && !isComponentName(tag) && tag.includes('-'))
164
326
  return false;
327
+ const boundary = {
328
+ container,
329
+ context: { ...settings, isRoot, inRaw: false },
330
+ prev: null,
331
+ next: null,
332
+ sides: [null],
333
+ atStart: true,
334
+ atEnd: true,
335
+ loneChild: true,
336
+ };
337
+ return isFree(boundary) || mayAlterRenderedWhitespace(boundary);
338
+ }
339
+ function separatorFor(run, boundary, settings) {
340
+ const sides = sidesFor(boundary);
341
+ const internal = {
342
+ container: boundary.container,
343
+ context: { ...settings, isRoot: boundary.isRoot, inRaw: false },
344
+ prev: boundary.prev,
345
+ next: boundary.next,
346
+ sides: sides ?? [],
347
+ atStart: boundary.prev === null,
348
+ atEnd: boundary.next === null,
349
+ loneChild: boundary.loneChild,
350
+ };
351
+ if (isSlotFallback(internal))
352
+ return run === '' ? 'none' : 'space';
353
+ const free = isFree(internal) || (sides !== null && mayAlterRenderedWhitespace(internal));
354
+ if (boundary.edge) {
355
+ if (free)
356
+ return 'soft';
357
+ return run === '' ? 'none' : 'space';
165
358
  }
166
- const children = node.children;
167
- if (children.length === 0) {
168
- return true;
359
+ if (isBlankRun(run))
360
+ return 'blank';
361
+ if (hasNewline(run))
362
+ return 'break';
363
+ if (free)
364
+ return 'soft';
365
+ return run === '' ? 'none' : 'space';
366
+ }
367
+ function normalizeWhitespace(template, options) {
368
+ visit(template, { ...options, isRoot: true, inRaw: false });
369
+ }
370
+ function visit(container, context) {
371
+ const children = childrenOf(container);
372
+ if (children === null)
373
+ return;
374
+ if (!context.inRaw)
375
+ collapseRuns(container, children, context);
376
+ for (const child of children) {
377
+ visit(child, {
378
+ ...context,
379
+ isRoot: false,
380
+ inRaw: context.inRaw || opensRawSubtree(child),
381
+ });
169
382
  }
170
- const firstChild = children[0];
171
- return !isTextNodeStartingWithWhitespace(firstChild);
172
383
  }
173
- function shouldHugEnd(node, opts) {
174
- if (isBlockElement(node, opts)) {
175
- return false;
384
+ function collapseRuns(container, children, context) {
385
+ const dropped = new Set();
386
+ for (const [index, child] of children.entries()) {
387
+ if (!isText(child))
388
+ continue;
389
+ const prev = children[index - 1] ?? null;
390
+ const next = children[index + 1] ?? null;
391
+ if (isWhitespaceOnly(child)) {
392
+ const decision = decide(rawTextOf(child), {
393
+ container,
394
+ context,
395
+ prev,
396
+ next,
397
+ sides: [prev, next],
398
+ atStart: prev === null,
399
+ atEnd: next === null,
400
+ loneChild: children.length === 1,
401
+ });
402
+ const text = resolve(rawTextOf(child), decision);
403
+ if (text === '')
404
+ dropped.add(child);
405
+ else
406
+ setText(child, text);
407
+ continue;
408
+ }
409
+ const raw = rawTextOf(child);
410
+ const lead = leadingWhitespace$1.exec(raw)?.[0] ?? '';
411
+ const trail = trailingWhitespace$1.exec(raw)?.[0] ?? '';
412
+ const body = raw.slice(lead.length, raw.length - trail.length);
413
+ const leadDecision = decide(lead, {
414
+ container,
415
+ context,
416
+ prev,
417
+ next: child,
418
+ sides: [prev],
419
+ atStart: prev === null,
420
+ atEnd: false,
421
+ loneChild: false,
422
+ });
423
+ const trailDecision = decide(trail, {
424
+ container,
425
+ context,
426
+ prev: child,
427
+ next,
428
+ sides: [next],
429
+ atStart: false,
430
+ atEnd: next === null,
431
+ loneChild: false,
432
+ });
433
+ setText(child, resolve(lead, leadDecision) + body + resolve(trail, trailDecision));
176
434
  }
177
- if (!isNodeWithChildren(node)) {
178
- return false;
435
+ if (dropped.size > 0) {
436
+ const kept = children.filter((child) => !dropped.has(child));
437
+ children.length = 0;
438
+ children.push(...kept);
179
439
  }
180
- const children = node.children;
181
- if (children.length === 0) {
440
+ }
441
+ function resolve(run, decision) {
442
+ if (decision === 'drop')
443
+ return hasNewline(run) ? run : '';
444
+ if (decision === 'space')
445
+ return ' ';
446
+ return run;
447
+ }
448
+ function setText(node, text) {
449
+ node.raw = text;
450
+ node.value = text;
451
+ }
452
+ function decide(run, boundary) {
453
+ if (run === '')
454
+ return 'keep';
455
+ const { mode } = boundary.context;
456
+ if (isSlotFallback(boundary))
457
+ return 'space';
458
+ if (isFree(boundary))
459
+ return 'drop';
460
+ if (mayAlterRenderedWhitespace(boundary))
461
+ return 'drop';
462
+ if (mode === 'jsx')
463
+ return hasNewline(run) ? 'keep' : 'space';
464
+ return boundary.atStart || boundary.atEnd ? 'space' : 'keep';
465
+ }
466
+ function isFree(boundary) {
467
+ const { container, context, atStart, atEnd, loneChild } = boundary;
468
+ const { mode } = context;
469
+ if (context.isRoot && (atStart || atEnd))
182
470
  return true;
183
- }
184
- const lastChild = children[children.length - 1];
185
- if (isExpressionNode(lastChild))
471
+ if (container.type === 'JSXElement' && hasSetDirective(container))
472
+ return true;
473
+ if (isExtractedStyle(boundary.prev) || isExtractedStyle(boundary.next))
186
474
  return true;
187
- if (isTagLikeNode(lastChild))
475
+ const tag = container.type === 'JSXElement' ? tagNameOf(container) : null;
476
+ if (loneChild && tag !== null && isComponentName(tag))
188
477
  return true;
189
- return !isTextNodeEndingWithWhitespace(lastChild);
478
+ if (mode === 'html') {
479
+ if (loneChild)
480
+ return true;
481
+ if (tag === 'head')
482
+ return true;
483
+ }
484
+ return false;
190
485
  }
191
- function canOmitSoftlineBeforeClosingTag(path, opts) {
192
- return isLastChildWithinParentBlockElement(path, opts);
486
+ function mayAlterRenderedWhitespace(boundary) {
487
+ const { sensitivity } = boundary.context;
488
+ if (sensitivity === 'ignore')
489
+ return true;
490
+ if (sensitivity === 'strict')
491
+ return false;
492
+ return boundary.sides.every((side) => !isInlineDisplay(displayOf(side ?? boundary.container)));
193
493
  }
194
- function getChildren(node) {
195
- return isNodeWithChildren(node) ? node.children : [];
494
+ function isSlotFallback(boundary) {
495
+ return (boundary.container.type === 'JSXElement' &&
496
+ tagNameOf(boundary.container) === 'slot' &&
497
+ boundary.loneChild);
196
498
  }
197
- function isLastChildWithinParentBlockElement(path, opts) {
198
- const parent = path.getParentNode();
199
- if (!parent || !isBlockElement(parent, opts)) {
200
- return false;
201
- }
202
- const children = getChildren(parent);
203
- const lastChild = children[children.length - 1];
204
- return lastChild === path.getNode();
499
+
500
+ function parse(source, options) {
501
+ const { ast, diagnostics } = parse$1(source);
502
+ const failure = diagnostics.find((diagnostic) => diagnostic.severity === 'error');
503
+ if (failure)
504
+ throw syntaxError(failure);
505
+ stripParenthesizedExpressions(ast);
506
+ repairSpans(ast);
507
+ markAttributes(ast, source);
508
+ applyPrettierIgnore(ast);
509
+ const body = ast.body;
510
+ const template = templateFragment(ast, body);
511
+ const settings = {
512
+ mode: options.astroCompressHTML,
513
+ sensitivity: options.htmlWhitespaceSensitivity,
514
+ };
515
+ if (settings.mode === 'jsx')
516
+ normalizeWhitespace(template.node, settings);
517
+ else
518
+ resolveBlankContainers(template.node, settings);
519
+ normalizeTagPairs(body, source);
520
+ if (settings.mode !== 'jsx')
521
+ claimChildren(template.node);
522
+ ast.template = template;
523
+ delete ast.body;
524
+ ast.comments = printableComments(ast);
525
+ return ast;
205
526
  }
206
- function trimTextNodeLeft(node) {
207
- node.value = node.value && node.value.trimStart();
527
+ function syntaxError(diagnostic) {
528
+ const label = diagnostic.labels[0];
529
+ const line = label?.line ?? 1;
530
+ const column = (label?.column ?? 0) + 1;
531
+ const error = new SyntaxError(`${diagnostic.text} (${line}:${column})`);
532
+ error.loc = { start: { line, column } };
533
+ return error;
208
534
  }
209
- function trimTextNodeRight(node) {
210
- node.value = node.value && node.value.trimEnd();
535
+ function stripParenthesizedExpressions(root) {
536
+ walk(root, (node) => {
537
+ for (const key of Object.keys(node)) {
538
+ const value = node[key];
539
+ if (Array.isArray(value)) {
540
+ for (const [index, item] of value.entries()) {
541
+ value[index] = unwrapParens(item);
542
+ }
543
+ }
544
+ else if (isNode(value)) {
545
+ node[key] = unwrapParens(value);
546
+ }
547
+ }
548
+ });
211
549
  }
212
- function printClassNames(value) {
213
- const lines = value.trim().split(/[\r\n]+/);
214
- const formattedLines = lines.map((line) => {
215
- const spaces = line.match(/^\s+/);
216
- return (spaces ? spaces[0] : '') + line.trim().split(/\s+/).join(' ');
550
+ function unwrapParens(node) {
551
+ let current = node;
552
+ while (isNode(current) && current.type === 'ParenthesizedExpression') {
553
+ current = current.expression;
554
+ }
555
+ return current;
556
+ }
557
+ function repairSpans(root) {
558
+ walk(root, (node) => {
559
+ if (node.type !== 'JSXElement')
560
+ return;
561
+ const script = node.children.find((child) => child.type === 'AstroScript');
562
+ if (!script)
563
+ return;
564
+ script.start = node.openingElement.end;
565
+ script.end = node.closingElement?.start ?? node.end;
217
566
  });
218
- return formattedLines.join('\n');
219
567
  }
220
- function manualDedent(input) {
221
- let minTabSize = Infinity;
222
- let result = input;
223
- result = result.replace(/\r\n/g, '\n');
224
- let char = '';
225
- for (const line of result.split('\n')) {
226
- if (!line)
227
- continue;
228
- if (line[0] && /^[^\s]/.test(line[0])) {
229
- minTabSize = 0;
230
- break;
568
+ function markAttributes(root, source) {
569
+ walk(root, (node) => {
570
+ if (node.type !== 'JSXAttribute')
571
+ return;
572
+ const value = node.value;
573
+ if (!value)
574
+ 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('"', '&quot;')}"`;
581
+ }
582
+ return;
231
583
  }
232
- const match = line.match(/^(\s+)\S+/);
233
- if (match) {
234
- if (match[1] && !char)
235
- char = match[1][0];
236
- if (match[1].length < minTabSize)
237
- minTabSize = match[1].length;
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
+ });
591
+ }
592
+ function applyPrettierIgnore(root) {
593
+ walk(root, (node) => {
594
+ const children = node.type === 'AstroRoot' ? node.body : childrenOf(node);
595
+ if (children === null)
596
+ return;
597
+ for (const [index, child] of children.entries()) {
598
+ if (child.type !== 'AstroComment')
599
+ continue;
600
+ if (String(child.value).trim() !== 'prettier-ignore')
601
+ continue;
602
+ const target = children.slice(index + 1).find((sibling) => sibling.type !== 'JSXText');
603
+ if (target)
604
+ target.astroIgnored = true;
238
605
  }
239
- }
240
- if (minTabSize > 0 && Number.isFinite(minTabSize)) {
241
- result = result.replace(new RegExp(`^${new Array(minTabSize + 1).join(char)}`, 'gm'), '');
242
- }
606
+ });
607
+ }
608
+ function templateFragment(root, body) {
609
+ const start = body[0]?.start ?? root.end;
610
+ const end = body.at(-1)?.end ?? root.end;
243
611
  return {
244
- tabSize: minTabSize === Infinity ? 0 : minTabSize,
245
- char,
246
- result,
612
+ type: 'JsExpressionRoot',
613
+ start,
614
+ end,
615
+ node: {
616
+ type: 'JSXFragment',
617
+ start,
618
+ end,
619
+ astroRoot: true,
620
+ openingFragment: { type: 'JSXOpeningFragment', start, end: start, [synthetic]: true },
621
+ children: body,
622
+ closingFragment: { type: 'JSXClosingFragment', start: end, end, [synthetic]: true },
623
+ },
247
624
  };
248
625
  }
249
- function isTextNode(node) {
250
- return node.type === 'text';
251
- }
252
- function isExpressionNode(node) {
253
- return node.type === 'expression';
254
- }
255
- function isTagLikeNode(node) {
256
- return (node.type === 'element' ||
257
- node.type === 'component' ||
258
- node.type === 'custom-element' ||
259
- node.type === 'fragment');
260
- }
261
- function getSiblings(path) {
262
- const parent = path.getParentNode();
263
- if (!parent)
264
- return [];
265
- return getChildren(parent);
266
- }
267
- function getNextNode(path) {
268
- const node = path.getNode();
269
- if (node) {
270
- const siblings = getSiblings(path);
271
- if (node.position?.start === siblings[siblings.length - 1].position?.start)
272
- return null;
273
- for (let i = 0; i < siblings.length; i++) {
274
- const sibling = siblings[i];
275
- if (sibling.position?.start === node.position?.start && i !== siblings.length - 1) {
276
- return siblings[i + 1];
626
+ function pairUp(node, tag) {
627
+ node.openingElement.selfClosing = false;
628
+ node.closingElement = {
629
+ type: 'JSXClosingElement',
630
+ start: node.end,
631
+ end: node.end,
632
+ name: { type: 'JSXIdentifier', name: tag, start: node.end, end: node.end },
633
+ };
634
+ }
635
+ function printsNothing(child) {
636
+ const raw = String(child.raw ?? '');
637
+ return child.type === 'JSXText' && raw.trim() === '' && /[\n\r]/.test(raw);
638
+ }
639
+ function normalizeTagPairs(body, source) {
640
+ walk(body, (node) => {
641
+ if (node.type !== 'JSXElement')
642
+ return;
643
+ const tag = tagNameOf(node);
644
+ if (tag === null)
645
+ return;
646
+ const children = node.children;
647
+ const closing = node.closingElement;
648
+ const component = isComponentName(tag);
649
+ if (rawTextElements.has(tag) && !component) {
650
+ if (!closing)
651
+ pairUp(node, tag);
652
+ else if (source.slice(node.openingElement.end, closing.start).trim() === '') {
653
+ children.length = 0;
277
654
  }
655
+ return;
278
656
  }
657
+ const selfClosable = component || voidElements.has(tag) || tag === 'slot' || hasSetDirective(node);
658
+ if (children.every(printsNothing) && selfClosable && closing) {
659
+ node.openingElement.selfClosing = true;
660
+ node.closingElement = null;
661
+ }
662
+ });
663
+ }
664
+ function resolveBlankContainers(template, settings) {
665
+ walk(template, (node) => {
666
+ if (node.type !== 'JSXElement' && node.type !== 'JSXFragment')
667
+ return;
668
+ if (node.type === 'JSXElement' && opensRawSubtree(node))
669
+ return;
670
+ const children = node.children;
671
+ if (children.length === 0)
672
+ return;
673
+ const blank = children.every((child) => child.type === 'JSXText' && String(child.raw ?? '').trim() === '');
674
+ if (!blank)
675
+ return;
676
+ const run = children.map((child) => String(child.raw ?? '')).join('');
677
+ const free = blankContentIsFree(node, run, node.astroRoot === true, settings);
678
+ children.length = 0;
679
+ if (!free) {
680
+ children.push({ type: 'JSXText', start: node.start, end: node.start, raw: ' ', value: ' ' });
681
+ }
682
+ });
683
+ }
684
+ function claimChildren(template) {
685
+ walk(template, (node) => {
686
+ if (node.type !== 'JSXElement' && node.type !== 'JSXFragment')
687
+ return;
688
+ if (node.type === 'JSXElement' && opensRawSubtree(node))
689
+ return;
690
+ takeOverChildren(node);
691
+ });
692
+ }
693
+ function printableComments(root) {
694
+ const comments = root.comments ?? [];
695
+ if (comments.length === 0)
696
+ return comments;
697
+ const frontmatter = root.frontmatter;
698
+ const skipped = [];
699
+ if (frontmatter.end > 0)
700
+ skipped.push([frontmatter.start, frontmatter.end]);
701
+ walk(root, (node) => {
702
+ if (node.astroIgnored) {
703
+ skipped.push([node.start, node.end]);
704
+ return;
705
+ }
706
+ if (!opensRawSubtree(node))
707
+ return;
708
+ const closing = node.closingElement;
709
+ skipped.push([node.openingElement.end, closing?.start ?? node.end]);
710
+ });
711
+ return comments.filter((comment) => !skipped.some(([start, end]) => comment.start >= start && comment.end <= end));
712
+ }
713
+
714
+ const estree = printers$1.estree;
715
+
716
+ const { fill, group: group$1, hardline: hardline$2, indent: indent$1, line, softline } = doc.builders;
717
+ const leadingWhitespace = /^[\t\n\f\r ]+/;
718
+ const trailingWhitespace = /[\t\n\f\r ]+$/;
719
+ const whitespaceRun = /[\t\n\f\r ]+/;
720
+ function docFor(separator) {
721
+ switch (separator) {
722
+ case 'none':
723
+ return null;
724
+ case 'soft':
725
+ return softline;
726
+ case 'space':
727
+ return line;
728
+ case 'break':
729
+ return hardline$2;
730
+ case 'blank':
731
+ return [hardline$2, hardline$2];
279
732
  }
280
- return null;
281
733
  }
282
- const isPreTagContent = (path) => {
283
- if (!path || !path.stack || !Array.isArray(path.stack))
284
- return false;
285
- return path.stack.some((node) => (node.type === 'element' && node.name.toLowerCase() === 'pre') ||
286
- (node.type === 'attribute' && !formattableAttributes.includes(node.name)));
734
+ function collect(children, docs) {
735
+ const items = [];
736
+ const runs = [];
737
+ let pending = '';
738
+ for (const [index, child] of children.entries()) {
739
+ if (child.type !== 'JSXText') {
740
+ runs.push(pending);
741
+ items.push({ node: child, words: null, doc: docs[index] });
742
+ pending = '';
743
+ continue;
744
+ }
745
+ const raw = String(child.raw ?? '');
746
+ if (raw.trim() === '') {
747
+ pending += raw;
748
+ continue;
749
+ }
750
+ const lead = leadingWhitespace.exec(raw)?.[0] ?? '';
751
+ const trail = trailingWhitespace.exec(raw)?.[0] ?? '';
752
+ runs.push(pending + lead);
753
+ items.push({
754
+ node: child,
755
+ words: raw.slice(lead.length, raw.length - trail.length).split(whitespaceRun),
756
+ doc: '',
757
+ });
758
+ pending = trail;
759
+ }
760
+ runs.push(pending);
761
+ return { items, runs };
762
+ }
763
+ function printChildren(path, options, print) {
764
+ 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);
770
+ if (items.length === 0)
771
+ return runs[0] === '' ? '' : ' ';
772
+ const isRoot = container.astroRoot === true;
773
+ const settings = {
774
+ mode: options.astroCompressHTML,
775
+ sensitivity: options.htmlWhitespaceSensitivity,
776
+ };
777
+ const separatorAt = (index, edge) => docFor(separatorFor(runs[index], {
778
+ container,
779
+ prev: items[index - 1]?.node ?? null,
780
+ next: items[index]?.node ?? null,
781
+ isRoot,
782
+ loneChild: false,
783
+ edge,
784
+ }, settings));
785
+ const parts = [''];
786
+ const append = (content) => parts.push([parts.pop(), content]);
787
+ const separate = (separator) => {
788
+ if (separator !== null)
789
+ parts.push(separator, '');
790
+ };
791
+ for (const [position, item] of items.entries()) {
792
+ if (position > 0)
793
+ separate(separatorAt(position, false));
794
+ if (item.words === null) {
795
+ append(item.doc);
796
+ continue;
797
+ }
798
+ for (const [wordPosition, word] of item.words.entries()) {
799
+ if (wordPosition > 0)
800
+ separate(line);
801
+ append(word);
802
+ }
803
+ }
804
+ const body = fill(parts);
805
+ if (isRoot)
806
+ return body;
807
+ return group$1([indent$1([separatorAt(0, true) ?? '', body]), separatorAt(items.length, true) ?? '']);
808
+ }
809
+
810
+ const { group, hardline: hardline$1, indent, join } = doc.builders;
811
+ const { replaceEndOfLine: replaceEndOfLine$1 } = doc.utils;
812
+ const styleParsers = {
813
+ css: 'css',
814
+ scss: 'scss',
815
+ less: 'less',
287
816
  };
288
- function getPreferredQuote(rawContent, preferredQuote) {
289
- const double = { quote: '"', regex: /"/g, escaped: '&quot;' };
290
- const single = { quote: "'", regex: /'/g, escaped: '&apos;' };
291
- const preferred = preferredQuote === "'" ? single : double;
292
- const alternate = preferred === single ? double : single;
293
- let result = preferred;
294
- if (rawContent.includes(preferred.quote) || rawContent.includes(alternate.quote)) {
295
- const numPreferredQuotes = (rawContent.match(preferred.regex) || []).length;
296
- const numAlternateQuotes = (rawContent.match(alternate.regex) || []).length;
297
- result = numPreferredQuotes > numAlternateQuotes ? alternate : preferred;
817
+ async function surfacingErrors(textToDoc, text, options) {
818
+ try {
819
+ return await textToDoc(text, options);
820
+ }
821
+ catch (error) {
822
+ process.env.PRETTIER_DEBUG = 'true';
823
+ throw error;
298
824
  }
299
- return result;
300
825
  }
301
826
  function inferParserByTypeAttribute(type) {
302
- if (!type) {
303
- return 'babel-ts';
304
- }
305
827
  switch (type) {
828
+ case null:
306
829
  case 'module':
307
830
  case 'text/javascript':
308
831
  case 'text/babel':
@@ -323,476 +846,207 @@ function inferParserByTypeAttribute(type) {
323
846
  return 'babel-ts';
324
847
  }
325
848
  }
326
-
327
- const { builders: { breakParent, dedent, fill, group: group$1, indent: indent$1, join: join$1, line: line$1, softline: softline$1, hardline: hardline$1, literalline, }, utils: { stripTrailingHardline: stripTrailingHardline$1 }, } = _doc;
328
- let ignoreNext = false;
329
- function print(path, opts, print) {
849
+ function contentOf(node, options) {
850
+ const start = node.openingElement.end;
851
+ const end = node.closingElement?.start ?? node.end;
852
+ return options.originalText.slice(start, end);
853
+ }
854
+ function wrapContent(print, content, isEmpty) {
855
+ return [
856
+ print('openingElement'),
857
+ indent([isEmpty ? '' : hardline$1, content]),
858
+ isEmpty ? '' : hardline$1,
859
+ print('closingElement'),
860
+ ];
861
+ }
862
+ function embedSass(source, options) {
863
+ const sassOptions = {
864
+ tabSize: options.tabWidth,
865
+ insertSpaces: !options.useTabs,
866
+ lineEnding: options.endOfLine.toUpperCase() === 'CRLF' ? 'CRLF' : 'LF',
867
+ };
868
+ const { result } = manualDedent(source);
869
+ return join(hardline$1, SassFormatter.Format(result, sassOptions).trim().split('\n'));
870
+ }
871
+ function embed(path, options) {
330
872
  const node = path.node;
331
- if (!node) {
332
- return '';
333
- }
334
- if (ignoreNext && !isEmptyTextNode(node)) {
335
- ignoreNext = false;
336
- return [
337
- opts.originalText
338
- .slice(opts.locStart(node), opts.locEnd(node))
339
- .split('\n')
340
- .map((lineContent, i) => (i == 0 ? [lineContent] : [literalline, lineContent]))
341
- .flat(),
873
+ if (node.astroIgnored)
874
+ return undefined;
875
+ if (node.type === 'AstroFrontmatter') {
876
+ if (node.end === 0 || options.astroSkipFrontmatter)
877
+ return undefined;
878
+ const fence = options.originalText.indexOf('---', node.start);
879
+ const source = options.originalText.slice(fence + 3, node.end - 3);
880
+ if (!source.trim())
881
+ return undefined;
882
+ return async (textToDoc) => [
883
+ '---',
884
+ hardline$1,
885
+ await surfacingErrors(textToDoc, source, { ...options, parser: 'babel-ts' }),
886
+ hardline$1,
887
+ '---',
342
888
  ];
343
889
  }
344
- if (typeof node === 'string') {
345
- return node;
890
+ if (node.type !== 'JSXElement')
891
+ return estree.embed(path, options);
892
+ const tag = tagNameOf(node);
893
+ if (tag === null || !node.closingElement)
894
+ return estree.embed(path, options);
895
+ const source = contentOf(node, options);
896
+ const isEmpty = source.trim() === '';
897
+ if (tag === 'script') {
898
+ if (isEmpty)
899
+ return estree.embed(path, options);
900
+ const parser = inferParserByTypeAttribute(attributeStringValue(node, 'type'));
901
+ return async (textToDoc, print) => wrapContent(print, await surfacingErrors(textToDoc, source, { ...options, parser }), false);
346
902
  }
347
- switch (node.type) {
348
- case 'root': {
349
- return [stripTrailingHardline$1(path.map(print, 'children')), hardline$1];
350
- }
351
- case 'text': {
352
- const rawText = getUnencodedText(node);
353
- if (isEmptyTextNode(node)) {
354
- const hasWhiteSpace = rawText.trim().length < getUnencodedText(node).length;
355
- const hasOneOrMoreNewlines = /\n/.test(getUnencodedText(node));
356
- const hasTwoOrMoreNewlines = /\n\r?\s*\n\r?/.test(getUnencodedText(node));
357
- if (hasTwoOrMoreNewlines) {
358
- return [hardline$1, hardline$1];
359
- }
360
- if (hasOneOrMoreNewlines) {
361
- return hardline$1;
362
- }
363
- if (hasWhiteSpace) {
364
- return line$1;
365
- }
366
- return '';
367
- }
368
- return fill(splitTextToDocs(node));
369
- }
370
- case 'component':
371
- case 'fragment':
372
- case 'custom-element':
373
- case 'element': {
374
- let isEmpty;
375
- if (!node.children) {
376
- isEmpty = true;
377
- }
378
- else {
379
- isEmpty = node.children.every((child) => isEmptyTextNode(child));
380
- }
381
- const isSelfClosingTag = isEmpty &&
382
- (node.type === 'component' ||
383
- selfClosingTags.includes(node.name) ||
384
- hasSetDirectives(node));
385
- const isSingleLinePerAttribute = opts.singleAttributePerLine && node.attributes.length > 1;
386
- const attributeLine = isSingleLinePerAttribute ? breakParent : '';
387
- const attributes = join$1(attributeLine, path.map(print, 'attributes'));
388
- if (isSelfClosingTag) {
389
- return group$1(['<', node.name, indent$1(attributes), line$1, `/>`]);
390
- }
391
- if (node.children) {
392
- const children = node.children;
393
- const firstChild = children[0];
394
- const lastChild = children[children.length - 1];
395
- let noHugSeparatorStart = softline$1;
396
- let noHugSeparatorEnd = softline$1;
397
- const hugStart = shouldHugStart(node, opts);
398
- const hugEnd = shouldHugEnd(node, opts);
399
- let body;
400
- if (isEmpty) {
401
- body =
402
- isInlineElement(path, opts, node) &&
403
- node.children.length &&
404
- isTextNodeStartingWithWhitespace(node.children[0]) &&
405
- !isPreTagContent(path)
406
- ? () => line$1
407
- : () => softline$1;
408
- }
409
- else if (isPreTagContent(path)) {
410
- body = () => printRaw(node);
411
- }
412
- else if (isInlineElement(path, opts, node) && !isPreTagContent(path)) {
413
- body = () => path.map(print, 'children');
414
- }
415
- else {
416
- body = () => path.map(print, 'children');
417
- }
418
- const openingTag = [
419
- '<',
420
- node.name,
421
- indent$1(group$1([
422
- attributes,
423
- hugStart
424
- ? ''
425
- : !isPreTagContent(path) && !opts.bracketSameLine
426
- ? dedent(softline$1)
427
- : '',
428
- ])),
429
- ];
430
- if (hugStart && hugEnd) {
431
- const huggedContent = [
432
- isSingleLinePerAttribute ? hardline$1 : softline$1,
433
- group$1(['>', body(), `</${node.name}`]),
434
- ];
435
- const omitSoftlineBeforeClosingTag = isEmpty || canOmitSoftlineBeforeClosingTag(path, opts);
436
- return group$1([
437
- ...openingTag,
438
- isEmpty ? group$1(huggedContent) : group$1(indent$1(huggedContent)),
439
- omitSoftlineBeforeClosingTag ? '' : softline$1,
440
- '>',
441
- ]);
442
- }
443
- if (isPreTagContent(path)) {
444
- noHugSeparatorStart = '';
445
- noHugSeparatorEnd = '';
446
- }
447
- else {
448
- let didSetEndSeparator = false;
449
- if (!hugStart && firstChild && isTextNode(firstChild)) {
450
- if (isTextNodeStartingWithLinebreak(firstChild) &&
451
- firstChild !== lastChild &&
452
- (!isInlineElement(path, opts, node) || isTextNodeEndingWithWhitespace(lastChild))) {
453
- noHugSeparatorStart = hardline$1;
454
- noHugSeparatorEnd = hardline$1;
455
- didSetEndSeparator = true;
456
- }
457
- else if (isInlineElement(path, opts, node)) {
458
- noHugSeparatorStart = line$1;
459
- }
460
- trimTextNodeLeft(firstChild);
461
- }
462
- if (!hugEnd && lastChild && isTextNode(lastChild)) {
463
- if (isInlineElement(path, opts, node) && !didSetEndSeparator) {
464
- noHugSeparatorEnd = line$1;
465
- }
466
- trimTextNodeRight(lastChild);
467
- }
468
- }
469
- if (hugStart) {
470
- return group$1([
471
- ...openingTag,
472
- indent$1([softline$1, group$1(['>', body()])]),
473
- noHugSeparatorEnd,
474
- `</${node.name}>`,
475
- ]);
476
- }
477
- if (hugEnd) {
478
- return group$1([
479
- ...openingTag,
480
- '>',
481
- indent$1([noHugSeparatorStart, group$1([body(), `</${node.name}`])]),
482
- canOmitSoftlineBeforeClosingTag(path, opts) ? '' : softline$1,
483
- '>',
484
- ]);
485
- }
486
- if (isEmpty) {
487
- return group$1([...openingTag, '>', body(), `</${node.name}>`]);
488
- }
489
- return group$1([
490
- ...openingTag,
491
- '>',
492
- indent$1([noHugSeparatorStart, body()]),
493
- noHugSeparatorEnd,
494
- `</${node.name}>`,
495
- ]);
496
- }
497
- return '';
498
- }
499
- case 'attribute': {
500
- const name = node.name.trim();
501
- switch (node.kind) {
502
- case 'empty':
503
- return [line$1, name];
504
- case 'expression':
505
- return '';
506
- case 'quoted':
507
- let value = node.value;
508
- if (node.name === 'class') {
509
- value = printClassNames(value);
510
- }
511
- const unescapedValue = value.replace(/&apos;/g, "'").replace(/&quot;/g, '"');
512
- const { escaped, quote, regex } = getPreferredQuote(unescapedValue, opts.jsxSingleQuote ? "'" : '"');
513
- const result = unescapedValue.replace(regex, escaped);
514
- return [line$1, name, '=', quote, result, quote];
515
- case 'shorthand':
516
- return [line$1, '{', name, '}'];
517
- case 'spread':
518
- return [line$1, '{...', name, '}'];
519
- case 'template-literal':
520
- return [line$1, name, '=', '`', node.value, '`'];
521
- }
522
- return '';
523
- }
524
- case 'doctype': {
525
- return ['<!doctype html>', hardline$1];
526
- }
527
- case 'comment':
528
- if (isIgnoreDirective(node)) {
529
- ignoreNext = true;
530
- }
531
- const nextNode = getNextNode(path);
532
- let trailingLine = '';
533
- if (nextNode && isTagLikeNode(nextNode)) {
534
- trailingLine = hardline$1;
535
- }
536
- return ['<!--', getUnencodedText(node), '-->', trailingLine];
537
- default: {
538
- throw new Error(`Unhandled node type "${node.type}"!`);
903
+ if (tag === 'style') {
904
+ if (isEmpty)
905
+ return estree.embed(path, options);
906
+ const lang = attributeStringValue(node, 'lang')?.toLowerCase() ?? 'css';
907
+ if (lang === 'sass') {
908
+ return (_textToDoc, print) => wrapContent(print, embedSass(source, options), false);
539
909
  }
910
+ const parser = styleParsers[lang];
911
+ if (!parser)
912
+ return printVerbatim(source);
913
+ return async (textToDoc, print) => wrapContent(print, await surfacingErrors(textToDoc, source, { ...options, parser }), false);
540
914
  }
541
- }
542
- function splitTextToDocs(node) {
543
- const text = getUnencodedText(node);
544
- const textLines = text.split(/[\t\n\f\r ]+/);
545
- let docs = join$1(line$1, textLines).filter((doc) => doc !== '');
546
- if (startsWithLinebreak(text)) {
547
- docs[0] = hardline$1;
548
- }
549
- if (startsWithLinebreak(text, 2)) {
550
- docs = [hardline$1, ...docs];
551
- }
552
- if (endsWithLinebreak(text)) {
553
- docs[docs.length - 1] = hardline$1;
554
- }
555
- if (endsWithLinebreak(text, 2)) {
556
- docs = [...docs, hardline$1];
915
+ if (opensRawSubtree(node)) {
916
+ return (_textToDoc, print) => [
917
+ print('openingElement'),
918
+ replaceEndOfLine$1(source),
919
+ print('closingElement'),
920
+ ];
557
921
  }
558
- return docs;
922
+ return estree.embed(path, options);
923
+ }
924
+ function printVerbatim(source) {
925
+ return (_textToDoc, print) => group([print('openingElement'), replaceEndOfLine$1(source), print('closingElement')]);
559
926
  }
560
927
 
561
- const { builders: { group, indent, join, line, softline, hardline, lineSuffixBoundary }, utils: { stripTrailingHardline, mapDoc }, } = _doc;
562
- const supportedStyleLangValues = ['css', 'scss', 'sass', 'less'];
563
- const embed = ((path, options) => {
564
- const parserOption = options;
565
- return async (textToDoc, print) => {
566
- const node = path.node;
567
- if (!node)
568
- return undefined;
569
- if (node.type === 'expression') {
570
- const jsxNode = makeNodeJSXCompatible(node);
571
- const textContent = printRaw(jsxNode);
572
- let content;
573
- content = await wrapParserTryCatch(textToDoc, textContent, {
574
- ...options,
575
- parser: 'astroExpressionParser',
576
- });
577
- content = stripTrailingHardline(content);
578
- const strings = [];
579
- mapDoc(content, (doc) => {
580
- if (typeof doc === 'string') {
581
- strings.push(doc);
582
- }
583
- });
584
- if (strings.every((value) => value.startsWith('//'))) {
585
- return group(['{', content, softline, lineSuffixBoundary, '}']);
586
- }
587
- const astroDoc = mapDoc(content, (doc) => {
588
- if (typeof doc === 'string') {
589
- doc = doc.replaceAll(openingBracketReplace, '{');
590
- doc = doc.replaceAll(closingBracketReplace, '}');
591
- doc = doc.replaceAll(atSignReplace, '@');
592
- doc = doc.replaceAll(dotReplace, '.');
593
- doc = doc.replaceAll(interrogationReplace, '?');
594
- }
595
- return doc;
596
- });
597
- return group(['{', indent([softline, astroDoc]), softline, lineSuffixBoundary, '}']);
598
- }
599
- if (node.type === 'attribute' && node.kind === 'expression') {
600
- const value = node.value.trim();
601
- const name = node.name.trim();
602
- const attrNodeValue = await wrapParserTryCatch(textToDoc, value, {
603
- ...options,
604
- parser: 'astroExpressionParser',
605
- });
606
- if (name === value && options.astroAllowShorthand) {
607
- return [line, '{', attrNodeValue, '}'];
928
+ const { hardline } = doc.builders;
929
+ const { replaceEndOfLine } = doc.utils;
930
+ function printDoctype(value) {
931
+ const trimmed = value.trim();
932
+ const space = trimmed.search(/\s/);
933
+ const name = space === -1 ? trimmed : trimmed.slice(0, space);
934
+ const rest = space === -1 ? '' : trimmed.slice(space);
935
+ return `<!doctype ${name.toLowerCase()}${rest}>`;
936
+ }
937
+ function printAstroNode(path, options, print) {
938
+ const node = path.node;
939
+ switch (node.type) {
940
+ case 'AstroRoot': {
941
+ const template = node.template;
942
+ const hasTemplate = template.node.children.length > 0;
943
+ const parts = [];
944
+ if (node.frontmatter.end > 0) {
945
+ parts.push(print('frontmatter'));
946
+ if (hasTemplate)
947
+ parts.push(hardline, hardline);
608
948
  }
609
- return [line, name, '=', '{', attrNodeValue, '}'];
949
+ if (hasTemplate)
950
+ parts.push(print('template'));
951
+ return [parts, hardline];
610
952
  }
611
- if (node.type === 'attribute' && node.kind === 'spread') {
612
- const spreadContent = await wrapParserTryCatch(textToDoc, node.name, {
613
- ...options,
614
- parser: 'astroExpressionParser',
615
- });
616
- return [line, '{...', spreadContent, '}'];
617
- }
618
- if (node.type === 'frontmatter') {
953
+ case 'AstroFrontmatter': {
954
+ const body = node.program.body;
619
955
  if (options.astroSkipFrontmatter) {
620
- return [group(['---', node.value, '---', hardline]), hardline];
621
- }
622
- const frontmatterContent = await wrapParserTryCatch(textToDoc, node.value, {
623
- ...options,
624
- parser: 'babel-ts',
625
- });
626
- return [group(['---', hardline, frontmatterContent, hardline, '---', hardline]), hardline];
627
- }
628
- if (node.type === 'element' && node.name === 'script' && node.children.length) {
629
- const typeAttribute = node.attributes.find((attr) => attr.name === 'type')?.value;
630
- let parser = 'babel-ts';
631
- if (typeAttribute) {
632
- parser = inferParserByTypeAttribute(typeAttribute);
633
- }
634
- const scriptContent = printRaw(node);
635
- let formattedScript = await wrapParserTryCatch(textToDoc, scriptContent, {
636
- ...options,
637
- parser: parser,
638
- });
639
- formattedScript = stripTrailingHardline(formattedScript);
640
- const isEmpty = /^\s*$/.test(scriptContent);
641
- const attributes = path.map(print, 'attributes');
642
- const openingTag = group(['<script', indent(group(attributes)), softline, '>']);
643
- return [
644
- openingTag,
645
- indent([isEmpty ? '' : hardline, formattedScript]),
646
- isEmpty ? '' : hardline,
647
- '</script>',
648
- ];
649
- }
650
- if (node.type === 'element' && node.name === 'style') {
651
- const content = printRaw(node);
652
- let parserLang = 'css';
653
- if (node.attributes) {
654
- const langAttribute = node.attributes.filter((x) => x.name === 'lang');
655
- if (langAttribute.length) {
656
- const styleLang = langAttribute[0].value.toLowerCase();
657
- parserLang = supportedStyleLangValues.includes(styleLang) ? styleLang : undefined;
658
- }
956
+ const fence = options.originalText.indexOf('---', node.start);
957
+ return replaceEndOfLine(options.originalText.slice(fence, node.end));
659
958
  }
660
- return await embedStyle(parserLang, content, path, print, textToDoc, parserOption);
959
+ return body.length > 0
960
+ ? ['---', hardline, print('program'), '---']
961
+ : ['---', hardline, '---'];
661
962
  }
662
- return undefined;
663
- };
664
- });
665
- async function wrapParserTryCatch(cb, text, options) {
666
- try {
667
- return await cb(text, options);
668
- }
669
- catch (e) {
670
- process.env.PRETTIER_DEBUG = 'true';
671
- throw e;
963
+ case 'AstroScript':
964
+ return node.program.body.length > 0 ? print('program') : '';
965
+ case 'AstroDoctype':
966
+ return printDoctype(node.value);
967
+ case 'AstroComment':
968
+ return `<!--${node.value}-->`;
969
+ default:
970
+ return '';
672
971
  }
673
972
  }
674
- function makeNodeJSXCompatible(node) {
675
- const newNode = { ...node };
676
- const childBundle = [];
677
- let childBundleIndex = 0;
678
- if (isNodeWithChildren(newNode)) {
679
- newNode.children = newNode.children.reduce((result, child, index) => {
680
- const previousChildren = newNode.children[index - 1];
681
- const nextChildren = newNode.children[index + 1];
682
- if (isTagLikeNode(child)) {
683
- child.attributes = child.attributes.map(makeAttributeJSXCompatible);
684
- if (!childBundle[childBundleIndex]) {
685
- childBundle[childBundleIndex] = [];
686
- }
687
- if (isNodeWithChildren(child)) {
688
- child = makeNodeJSXCompatible(child);
689
- }
690
- if ((!previousChildren || isTextNode(previousChildren)) &&
691
- nextChildren &&
692
- isTagLikeNode(nextChildren)) {
693
- childBundle[childBundleIndex].push(child);
694
- return result;
695
- }
696
- if (previousChildren &&
697
- isTagLikeNode(previousChildren) &&
698
- nextChildren &&
699
- isTagLikeNode(nextChildren)) {
700
- childBundle[childBundleIndex].push(child);
701
- return result;
702
- }
703
- if ((!nextChildren || isTextNode(nextChildren)) &&
704
- childBundle[childBundleIndex].length > 0) {
705
- childBundle[childBundleIndex].push(child);
706
- const parentNode = {
707
- type: 'fragment',
708
- name: '',
709
- attributes: [],
710
- children: childBundle[childBundleIndex],
711
- };
712
- childBundleIndex += 1;
713
- result.push(parentNode);
714
- return result;
715
- }
716
- }
717
- else {
718
- childBundleIndex += 1;
719
- }
720
- result.push(child);
721
- return result;
722
- }, []);
973
+ function printAttribute(path, options, print) {
974
+ const node = path.node;
975
+ const name = node.name.name;
976
+ const value = node.value;
977
+ if (node.astroShorthand)
978
+ return ['{', print(['value', 'expression']), '}'];
979
+ if (node.astroBacktick)
980
+ return [name, '=', print(['value', 'expression'])];
981
+ const expression = value?.type === 'JSXExpressionContainer' ? value.expression : null;
982
+ if (options.astroAllowShorthand &&
983
+ expression?.type === 'Identifier' &&
984
+ expression.name === name) {
985
+ return ['{', print(['value', 'expression']), '}'];
723
986
  }
724
- return newNode;
725
- function makeAttributeJSXCompatible(attr) {
726
- if (attr.kind === 'shorthand') {
727
- attr.kind = 'empty';
728
- attr.name = openingBracketReplace + attr.name + closingBracketReplace;
729
- }
730
- if (attr.kind !== 'spread') {
731
- if (attr.name.includes('@')) {
732
- attr.name = attr.name.replaceAll('@', atSignReplace);
733
- }
734
- if (attr.name.includes('.')) {
735
- attr.name = attr.name.replaceAll('.', dotReplace);
736
- }
737
- if (attr.name.includes('?')) {
738
- attr.name = attr.name.replaceAll('?', interrogationReplace);
739
- }
740
- }
741
- return attr;
987
+ return null;
988
+ }
989
+ function unwrapFragmentIndent(printed) {
990
+ const group = printed;
991
+ if (group.type !== 'group')
992
+ return printed;
993
+ if (group.expandedStates) {
994
+ const states = group.expandedStates.map(unwrapFragmentIndent);
995
+ return { ...group, contents: states[0], expandedStates: states };
742
996
  }
997
+ const parts = group.contents;
998
+ if (!Array.isArray(parts) || parts.length !== 4)
999
+ return printed;
1000
+ const indented = parts[1];
1001
+ if (indented.type !== 'indent' || !indented.contents)
1002
+ return printed;
1003
+ return indented.contents[1];
743
1004
  }
744
- async function embedStyle(lang, content, path, print, textToDoc, options) {
745
- const isEmpty = /^\s*$/.test(content);
746
- switch (lang) {
747
- case 'less':
748
- case 'css':
749
- case 'scss': {
750
- let formattedStyles = await wrapParserTryCatch(textToDoc, content, {
751
- ...options,
752
- parser: lang,
753
- });
754
- formattedStyles = stripTrailingHardline(formattedStyles);
755
- const attributes = path.map(print, 'attributes');
756
- const openingTag = group(['<style', indent(group(attributes)), softline, '>']);
757
- return [
758
- openingTag,
759
- indent([isEmpty ? '' : hardline, formattedStyles]),
760
- isEmpty ? '' : hardline,
761
- '</style>',
762
- ];
1005
+ const printer = {
1006
+ ...estree,
1007
+ embed,
1008
+ getVisitorKeys(node, nonTraversableKeys) {
1009
+ const keys = astroVisitorKeys[node.type] ?? estree.getVisitorKeys(node, nonTraversableKeys);
1010
+ return node.astroChildren
1011
+ ? keys.map((key) => (key === 'children' ? 'astroChildren' : key))
1012
+ : keys;
1013
+ },
1014
+ print(path, options, print, args) {
1015
+ const node = path.node;
1016
+ if (node[ownChildren])
1017
+ return path.callParent(() => printChildren(path, options, print));
1018
+ if (node[synthetic])
1019
+ return '';
1020
+ if (node.astroIgnored) {
1021
+ return replaceEndOfLine(options.originalText.slice(node.start, node.end));
763
1022
  }
764
- case 'sass': {
765
- const lineEnding = options?.endOfLine?.toUpperCase() === 'CRLF' ? 'CRLF' : 'LF';
766
- const sassOptions = {
767
- tabSize: options.tabWidth,
768
- insertSpaces: !options.useTabs,
769
- lineEnding,
770
- };
771
- const { result: raw } = manualDedent(content);
772
- const formattedSassIndented = SassFormatter.Format(raw, sassOptions).trim();
773
- const formattedSass = join(hardline, formattedSassIndented.split('\n'));
774
- const attributes = path.map(print, 'attributes');
775
- const openingTag = group(['<style', indent(group(attributes)), softline, '>']);
1023
+ if (astroVisitorKeys[node.type])
1024
+ return printAstroNode(path, options, print);
1025
+ if (node.type === 'JSXAttribute') {
1026
+ const attribute = printAttribute(path, options, print);
1027
+ if (attribute)
1028
+ return attribute;
1029
+ }
1030
+ if (node.type === 'JSXElement' &&
1031
+ node.closingElement &&
1032
+ node.children.length > 0 &&
1033
+ opensRawSubtree(node)) {
1034
+ const start = node.openingElement.end;
1035
+ const end = node.closingElement.start;
776
1036
  return [
777
- openingTag,
778
- indent([isEmpty ? '' : hardline, formattedSass]),
779
- isEmpty ? '' : hardline,
780
- '</style>',
1037
+ print('openingElement'),
1038
+ replaceEndOfLine(options.originalText.slice(start, end)),
1039
+ print('closingElement'),
781
1040
  ];
782
1041
  }
783
- case undefined: {
784
- const node = path.getNode();
785
- if (node) {
786
- return Buffer.from(options.originalText)
787
- .subarray(options.locStart(node), options.locEnd(node))
788
- .toString();
789
- }
790
- return undefined;
1042
+ const printed = estree.print(path, options, print, args);
1043
+ if (node.openingFragment?.[synthetic]) {
1044
+ return unwrapFragmentIndent(printed);
791
1045
  }
792
- }
793
- }
1046
+ return printed;
1047
+ },
1048
+ };
794
1049
 
795
- const babelParser = prettierPluginBabel.parsers['babel-ts'];
796
1050
  const languages = [
797
1051
  {
798
1052
  name: 'astro',
@@ -803,30 +1057,14 @@ const languages = [
803
1057
  ];
804
1058
  const parsers = {
805
1059
  astro: {
806
- parse: (source) => parse(source, { position: true }).ast,
1060
+ parse,
807
1061
  astFormat: 'astro',
808
- locStart: (node) => node.position.start.offset,
809
- locEnd: (node) => node.position.end.offset,
810
- },
811
- astroExpressionParser: {
812
- ...babelParser,
813
- preprocess(text) {
814
- return `<>{${text}\n}</>`;
815
- },
816
- parse(text, opts) {
817
- const ast = babelParser.parse(text, opts);
818
- return {
819
- ...ast,
820
- program: ast.program.body[0].expression.children[0].expression,
821
- };
822
- },
1062
+ locStart: (node) => node.start,
1063
+ locEnd: (node) => node.end,
823
1064
  },
824
1065
  };
825
1066
  const printers = {
826
- astro: {
827
- print,
828
- embed,
829
- },
1067
+ astro: printer,
830
1068
  };
831
1069
  const defaultOptions = {
832
1070
  tabWidth: 2,