prettier-plugin-astro 0.14.1 → 1.0.0-beta.1

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,692 @@ 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, 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))
186
472
  return true;
187
- if (isTagLikeNode(lastChild))
473
+ if (isExtractedStyle(boundary.prev) || isExtractedStyle(boundary.next))
188
474
  return true;
189
- return !isTextNodeEndingWithWhitespace(lastChild);
190
- }
191
- function canOmitSoftlineBeforeClosingTag(path, opts) {
192
- return isLastChildWithinParentBlockElement(path, opts);
193
- }
194
- function getChildren(node) {
195
- return isNodeWithChildren(node) ? node.children : [];
475
+ const tag = container.type === 'JSXElement' ? tagNameOf(container) : null;
476
+ if (loneChild && tag !== null && isComponentName(tag))
477
+ return true;
478
+ if (mode === 'html') {
479
+ if (loneChild)
480
+ return true;
481
+ if (tag === 'head')
482
+ return true;
483
+ }
484
+ return false;
196
485
  }
197
- function isLastChildWithinParentBlockElement(path, opts) {
198
- const parent = path.getParentNode();
199
- if (!parent || !isBlockElement(parent, opts)) {
486
+ function mayAlterRenderedWhitespace(boundary) {
487
+ const { sensitivity } = boundary.context;
488
+ if (sensitivity === 'ignore')
489
+ return true;
490
+ if (sensitivity === 'strict')
200
491
  return false;
201
- }
202
- const children = getChildren(parent);
203
- const lastChild = children[children.length - 1];
204
- return lastChild === path.getNode();
492
+ return boundary.sides.every((side) => !isInlineDisplay(displayOf(side ?? boundary.container)));
205
493
  }
206
- function trimTextNodeLeft(node) {
207
- node.value = node.value && node.value.trimStart();
494
+ function isSlotFallback(boundary) {
495
+ return (boundary.container.type === 'JSXElement' &&
496
+ tagNameOf(boundary.container) === 'slot' &&
497
+ boundary.loneChild);
208
498
  }
209
- function trimTextNodeRight(node) {
210
- node.value = node.value && node.value.trimEnd();
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
+ stripParentheses(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;
211
526
  }
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(' ');
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;
534
+ }
535
+ function stripParentheses(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
+ }
217
548
  });
218
- return formattedLines.join('\n');
219
549
  }
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;
550
+ function unwrapParens(node) {
551
+ let current = node;
552
+ while (isNode(current)) {
553
+ if (current.type === 'ParenthesizedExpression')
554
+ current = current.expression;
555
+ else if (current.type === 'TSParenthesizedType')
556
+ current = current.typeAnnotation;
557
+ else
230
558
  break;
559
+ }
560
+ return current;
561
+ }
562
+ function repairSpans(root) {
563
+ walk(root, (node) => {
564
+ if (node.type !== 'JSXElement')
565
+ return;
566
+ const script = node.children.find((child) => child.type === 'AstroScript');
567
+ if (!script)
568
+ return;
569
+ script.start = node.openingElement.end;
570
+ script.end = node.closingElement?.start ?? node.end;
571
+ });
572
+ }
573
+ function markAttributes(root, source) {
574
+ walk(root, (node) => {
575
+ if (node.type !== 'JSXAttribute')
576
+ return;
577
+ const value = node.value;
578
+ if (!value)
579
+ return;
580
+ if (value.type === 'Literal' && typeof value.value === 'string') {
581
+ const name = node.name.name;
582
+ const text = name === 'class' ? printClassNames(value.value) : value.value;
583
+ if (value.raw === null || text !== value.value) {
584
+ value.value = text;
585
+ value.raw = `"${text.replaceAll('"', '&quot;')}"`;
586
+ }
587
+ return;
231
588
  }
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;
589
+ if (value.type !== 'JSXExpressionContainer')
590
+ return;
591
+ if (source[node.start] === '{')
592
+ node.astroShorthand = true;
593
+ if (source[value.start] === '`')
594
+ node.astroBacktick = true;
595
+ });
596
+ }
597
+ function applyPrettierIgnore(root) {
598
+ walk(root, (node) => {
599
+ const children = node.type === 'AstroRoot' ? node.body : childrenOf(node);
600
+ if (children === null)
601
+ return;
602
+ for (const [index, child] of children.entries()) {
603
+ if (child.type !== 'AstroComment')
604
+ continue;
605
+ if (String(child.value).trim() !== 'prettier-ignore')
606
+ continue;
607
+ const target = children.slice(index + 1).find((sibling) => sibling.type !== 'JSXText');
608
+ if (target)
609
+ target.astroIgnored = true;
238
610
  }
239
- }
240
- if (minTabSize > 0 && Number.isFinite(minTabSize)) {
241
- result = result.replace(new RegExp(`^${new Array(minTabSize + 1).join(char)}`, 'gm'), '');
242
- }
611
+ });
612
+ }
613
+ function templateFragment(root, body) {
614
+ const start = body[0]?.start ?? root.end;
615
+ const end = body.at(-1)?.end ?? root.end;
243
616
  return {
244
- tabSize: minTabSize === Infinity ? 0 : minTabSize,
245
- char,
246
- result,
617
+ type: 'JsExpressionRoot',
618
+ start,
619
+ end,
620
+ node: {
621
+ type: 'JSXFragment',
622
+ start,
623
+ end,
624
+ astroRoot: true,
625
+ openingFragment: { type: 'JSXOpeningFragment', start, end: start, [synthetic]: true },
626
+ children: body,
627
+ closingFragment: { type: 'JSXClosingFragment', start: end, end, [synthetic]: true },
628
+ },
247
629
  };
248
630
  }
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];
631
+ function pairUp(node, tag) {
632
+ node.openingElement.selfClosing = false;
633
+ node.closingElement = {
634
+ type: 'JSXClosingElement',
635
+ start: node.end,
636
+ end: node.end,
637
+ name: { type: 'JSXIdentifier', name: tag, start: node.end, end: node.end },
638
+ };
639
+ }
640
+ function printsNothing(child) {
641
+ const raw = String(child.raw ?? '');
642
+ return child.type === 'JSXText' && raw.trim() === '' && /[\n\r]/.test(raw);
643
+ }
644
+ function normalizeTagPairs(body, source) {
645
+ walk(body, (node) => {
646
+ if (node.type !== 'JSXElement')
647
+ return;
648
+ const tag = tagNameOf(node);
649
+ if (tag === null)
650
+ return;
651
+ const children = node.children;
652
+ const closing = node.closingElement;
653
+ const component = isComponentName(tag);
654
+ if (rawTextElements.has(tag) && !component) {
655
+ if (!closing)
656
+ pairUp(node, tag);
657
+ else if (source.slice(node.openingElement.end, closing.start).trim() === '') {
658
+ children.length = 0;
277
659
  }
660
+ return;
661
+ }
662
+ const selfClosable = component || voidElements.has(tag) || tag === 'slot' || hasSetDirective(node);
663
+ if (children.every(printsNothing) && selfClosable && closing) {
664
+ node.openingElement.selfClosing = true;
665
+ node.closingElement = null;
278
666
  }
667
+ });
668
+ }
669
+ function resolveBlankContainers(template, settings) {
670
+ walk(template, (node) => {
671
+ if (node.type !== 'JSXElement' && node.type !== 'JSXFragment')
672
+ return;
673
+ if (node.type === 'JSXElement' && opensRawSubtree(node))
674
+ return;
675
+ const children = node.children;
676
+ if (children.length === 0)
677
+ return;
678
+ const blank = children.every((child) => child.type === 'JSXText' && String(child.raw ?? '').trim() === '');
679
+ if (!blank)
680
+ return;
681
+ const free = blankContentIsFree(node, node.astroRoot === true, settings);
682
+ children.length = 0;
683
+ if (!free) {
684
+ children.push({ type: 'JSXText', start: node.start, end: node.start, raw: ' ', value: ' ' });
685
+ }
686
+ });
687
+ }
688
+ function claimChildren(template) {
689
+ walk(template, (node) => {
690
+ if (node.type !== 'JSXElement' && node.type !== 'JSXFragment')
691
+ return;
692
+ if (node.type === 'JSXElement' && opensRawSubtree(node))
693
+ return;
694
+ takeOverChildren(node);
695
+ });
696
+ }
697
+ function printableComments(root) {
698
+ const comments = root.comments ?? [];
699
+ if (comments.length === 0)
700
+ return comments;
701
+ const frontmatter = root.frontmatter;
702
+ const skipped = [];
703
+ if (frontmatter.end > 0)
704
+ skipped.push([frontmatter.start, frontmatter.end]);
705
+ walk(root, (node) => {
706
+ if (node.astroIgnored) {
707
+ skipped.push([node.start, node.end]);
708
+ return;
709
+ }
710
+ if (!opensRawSubtree(node))
711
+ return;
712
+ const closing = node.closingElement;
713
+ skipped.push([node.openingElement.end, closing?.start ?? node.end]);
714
+ });
715
+ return comments.filter((comment) => !skipped.some(([start, end]) => comment.start >= start && comment.end <= end));
716
+ }
717
+
718
+ const estree = printers$1.estree;
719
+
720
+ const { fill, group: group$1, hardline: hardline$2, indent: indent$1, line, softline } = doc.builders;
721
+ const leadingWhitespace = /^[\t\n\f\r ]+/;
722
+ const trailingWhitespace = /[\t\n\f\r ]+$/;
723
+ const whitespaceRun = /[\t\n\f\r ]+/;
724
+ function docFor(separator) {
725
+ switch (separator) {
726
+ case 'none':
727
+ return null;
728
+ case 'soft':
729
+ return softline;
730
+ case 'space':
731
+ return line;
732
+ case 'break':
733
+ return hardline$2;
734
+ case 'blank':
735
+ return [hardline$2, hardline$2];
279
736
  }
280
- return null;
281
737
  }
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)));
738
+ function collect(children, docs) {
739
+ const items = [];
740
+ const runs = [];
741
+ let pending = '';
742
+ for (const [index, child] of children.entries()) {
743
+ if (child.type !== 'JSXText') {
744
+ runs.push(pending);
745
+ items.push({ node: child, words: null, doc: docs[index] });
746
+ pending = '';
747
+ continue;
748
+ }
749
+ const raw = String(child.raw ?? '');
750
+ if (raw.trim() === '') {
751
+ pending += raw;
752
+ continue;
753
+ }
754
+ const lead = leadingWhitespace.exec(raw)?.[0] ?? '';
755
+ const trail = trailingWhitespace.exec(raw)?.[0] ?? '';
756
+ runs.push(pending + lead);
757
+ items.push({
758
+ node: child,
759
+ words: raw.slice(lead.length, raw.length - trail.length).split(whitespaceRun),
760
+ doc: '',
761
+ });
762
+ pending = trail;
763
+ }
764
+ runs.push(pending);
765
+ return { items, runs };
766
+ }
767
+ function printChildren(path, options, print) {
768
+ const container = path.node;
769
+ const docs = [];
770
+ path.each((child) => {
771
+ docs.push(child.node.type === 'JSXText' ? '' : print());
772
+ }, 'astroChildren');
773
+ const { items, runs } = collect(container.astroChildren, docs);
774
+ if (items.length === 0)
775
+ return runs[0] === '' ? '' : ' ';
776
+ const isRoot = container.astroRoot === true;
777
+ const settings = {
778
+ mode: options.astroCompressHTML,
779
+ sensitivity: options.htmlWhitespaceSensitivity,
780
+ };
781
+ const separatorAt = (index, edge) => docFor(separatorFor(runs[index], {
782
+ container,
783
+ prev: items[index - 1]?.node ?? null,
784
+ next: items[index]?.node ?? null,
785
+ isRoot,
786
+ loneChild: false,
787
+ edge,
788
+ }, settings));
789
+ const parts = [''];
790
+ const append = (content) => parts.push([parts.pop(), content]);
791
+ const separate = (separator) => {
792
+ if (separator !== null)
793
+ parts.push(separator, '');
794
+ };
795
+ for (const [position, item] of items.entries()) {
796
+ if (position > 0)
797
+ separate(separatorAt(position, false));
798
+ if (item.words === null) {
799
+ append(item.doc);
800
+ continue;
801
+ }
802
+ for (const [wordPosition, word] of item.words.entries()) {
803
+ if (wordPosition > 0)
804
+ separate(line);
805
+ append(word);
806
+ }
807
+ }
808
+ const body = fill(parts);
809
+ if (isRoot)
810
+ return body;
811
+ return group$1([indent$1([separatorAt(0, true) ?? '', body]), separatorAt(items.length, true) ?? '']);
812
+ }
813
+
814
+ const { group, hardline: hardline$1, indent, join } = doc.builders;
815
+ const { replaceEndOfLine: replaceEndOfLine$1 } = doc.utils;
816
+ const styleParsers = {
817
+ css: 'css',
818
+ scss: 'scss',
819
+ less: 'less',
287
820
  };
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;
821
+ async function surfacingErrors(textToDoc, text, options) {
822
+ try {
823
+ return await textToDoc(text, options);
824
+ }
825
+ catch (error) {
826
+ process.env.PRETTIER_DEBUG = 'true';
827
+ throw error;
298
828
  }
299
- return result;
300
829
  }
301
830
  function inferParserByTypeAttribute(type) {
302
- if (!type) {
303
- return 'babel-ts';
304
- }
305
831
  switch (type) {
832
+ case null:
306
833
  case 'module':
307
834
  case 'text/javascript':
308
835
  case 'text/babel':
@@ -323,476 +850,213 @@ function inferParserByTypeAttribute(type) {
323
850
  return 'babel-ts';
324
851
  }
325
852
  }
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) {
853
+ function inferScriptParser(node) {
854
+ const processed = attributesOf(node).every((attribute) => attribute.type === 'JSXAttribute' && attribute.name.name === 'src');
855
+ if (processed)
856
+ return 'babel-ts';
857
+ return inferParserByTypeAttribute(attributeStringValue(node, 'type'));
858
+ }
859
+ function contentOf(node, options) {
860
+ const start = node.openingElement.end;
861
+ const end = node.closingElement?.start ?? node.end;
862
+ return options.originalText.slice(start, end);
863
+ }
864
+ function wrapContent(print, content, isEmpty) {
865
+ return [
866
+ print('openingElement'),
867
+ indent([hardline$1, content]),
868
+ isEmpty ? '' : hardline$1,
869
+ print('closingElement'),
870
+ ];
871
+ }
872
+ function embedSass(source, options) {
873
+ const sassOptions = {
874
+ tabSize: options.tabWidth,
875
+ insertSpaces: !options.useTabs,
876
+ lineEnding: options.endOfLine.toUpperCase() === 'CRLF' ? 'CRLF' : 'LF',
877
+ };
878
+ const { result } = manualDedent(source);
879
+ return join(hardline$1, SassFormatter.Format(result, sassOptions).trim().split('\n'));
880
+ }
881
+ function embed(path, options) {
330
882
  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(),
883
+ if (node.astroIgnored)
884
+ return undefined;
885
+ if (node.type === 'AstroFrontmatter') {
886
+ if (node.end === 0 || options.astroSkipFrontmatter)
887
+ return undefined;
888
+ const fence = options.originalText.indexOf('---', node.start);
889
+ const source = options.originalText.slice(fence + 3, node.end - 3);
890
+ if (!source.trim())
891
+ return undefined;
892
+ return async (textToDoc) => [
893
+ '---',
894
+ hardline$1,
895
+ await surfacingErrors(textToDoc, source, { ...options, parser: 'babel-ts' }),
896
+ hardline$1,
897
+ '---',
342
898
  ];
343
899
  }
344
- if (typeof node === 'string') {
345
- return node;
900
+ if (node.type !== 'JSXElement')
901
+ return estree.embed(path, options);
902
+ const tag = tagNameOf(node);
903
+ if (tag === null || !node.closingElement)
904
+ return estree.embed(path, options);
905
+ const source = contentOf(node, options);
906
+ const isEmpty = source.trim() === '';
907
+ if (tag === 'script') {
908
+ if (isEmpty)
909
+ return estree.embed(path, options);
910
+ const parser = inferScriptParser(node);
911
+ return async (textToDoc, print) => wrapContent(print, await surfacingErrors(textToDoc, source, { ...options, parser }), false);
346
912
  }
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}"!`);
913
+ if (tag === 'style') {
914
+ if (isEmpty)
915
+ return estree.embed(path, options);
916
+ const lang = attributeStringValue(node, 'lang')?.toLowerCase() ?? 'css';
917
+ if (lang === 'sass') {
918
+ return (_textToDoc, print) => wrapContent(print, embedSass(source, options), false);
539
919
  }
920
+ const parser = styleParsers[lang];
921
+ if (!parser)
922
+ return printVerbatim(source);
923
+ return async (textToDoc, print) => wrapContent(print, await surfacingErrors(textToDoc, source, { ...options, parser }), false);
540
924
  }
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];
925
+ if (opensRawSubtree(node)) {
926
+ return (_textToDoc, print) => [
927
+ print('openingElement'),
928
+ replaceEndOfLine$1(source),
929
+ print('closingElement'),
930
+ ];
557
931
  }
558
- return docs;
932
+ return estree.embed(path, options);
933
+ }
934
+ function printVerbatim(source) {
935
+ return (_textToDoc, print) => group([print('openingElement'), replaceEndOfLine$1(source), print('closingElement')]);
559
936
  }
560
937
 
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, '}'];
938
+ const { hardline } = doc.builders;
939
+ const { replaceEndOfLine } = doc.utils;
940
+ function printDoctype(value) {
941
+ const trimmed = value.trim();
942
+ const space = trimmed.search(/\s/);
943
+ const name = space === -1 ? trimmed : trimmed.slice(0, space);
944
+ const rest = space === -1 ? '' : trimmed.slice(space);
945
+ return `<!doctype ${name.toLowerCase()}${rest}>`;
946
+ }
947
+ function printAstroNode(path, options, print) {
948
+ const node = path.node;
949
+ switch (node.type) {
950
+ case 'AstroRoot': {
951
+ const template = node.template;
952
+ const hasTemplate = template.node.children.length > 0;
953
+ const parts = [];
954
+ if (node.frontmatter.end > 0) {
955
+ parts.push(print('frontmatter'));
956
+ if (hasTemplate)
957
+ parts.push(hardline, hardline);
608
958
  }
609
- return [line, name, '=', '{', attrNodeValue, '}'];
610
- }
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, '}'];
959
+ if (hasTemplate)
960
+ parts.push(print('template'));
961
+ return [parts, hardline];
617
962
  }
618
- if (node.type === 'frontmatter') {
963
+ case 'AstroFrontmatter': {
964
+ const body = node.program.body;
619
965
  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
- }
966
+ const fence = options.originalText.indexOf('---', node.start);
967
+ return replaceEndOfLine(options.originalText.slice(fence, node.end));
659
968
  }
660
- return await embedStyle(parserLang, content, path, print, textToDoc, parserOption);
969
+ return body.length > 0
970
+ ? ['---', hardline, print('program'), '---']
971
+ : ['---', hardline, '---'];
661
972
  }
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;
973
+ case 'AstroScript':
974
+ return node.program.body.length > 0 ? print('program') : '';
975
+ case 'AstroDoctype':
976
+ return printDoctype(node.value);
977
+ case 'AstroComment':
978
+ return `<!--${node.value}-->`;
979
+ default:
980
+ return '';
672
981
  }
673
982
  }
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
- }, []);
983
+ function printAttribute(path, options, print) {
984
+ const node = path.node;
985
+ const name = node.name.name;
986
+ const value = node.value;
987
+ if (node.astroShorthand)
988
+ return ['{', print(['value', 'expression']), '}'];
989
+ if (node.astroBacktick)
990
+ return [name, '=', print(['value', 'expression'])];
991
+ const expression = value?.type === 'JSXExpressionContainer' ? value.expression : null;
992
+ if (options.astroAllowShorthand &&
993
+ expression?.type === 'Identifier' &&
994
+ expression.name === name) {
995
+ return ['{', print(['value', 'expression']), '}'];
723
996
  }
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;
997
+ return null;
998
+ }
999
+ function unwrapFragmentIndent(printed) {
1000
+ const group = printed;
1001
+ if (group.type !== 'group')
1002
+ return printed;
1003
+ if (group.expandedStates) {
1004
+ const states = group.expandedStates.map(unwrapFragmentIndent);
1005
+ return { ...group, contents: states[0], expandedStates: states };
742
1006
  }
1007
+ const parts = group.contents;
1008
+ if (!Array.isArray(parts) || parts.length !== 4)
1009
+ return printed;
1010
+ const indented = parts[1];
1011
+ if (indented.type !== 'indent' || !indented.contents)
1012
+ return printed;
1013
+ return indented.contents[1];
743
1014
  }
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
- ];
1015
+ const printer = {
1016
+ ...estree,
1017
+ embed,
1018
+ getVisitorKeys(node, nonTraversableKeys) {
1019
+ const keys = astroVisitorKeys[node.type] ?? estree.getVisitorKeys(node, nonTraversableKeys);
1020
+ return node.astroChildren
1021
+ ? keys.map((key) => (key === 'children' ? 'astroChildren' : key))
1022
+ : keys;
1023
+ },
1024
+ print(path, options, print, args) {
1025
+ const node = path.node;
1026
+ if (node[ownChildren])
1027
+ return path.callParent(() => printChildren(path, options, print));
1028
+ if (node[synthetic])
1029
+ return '';
1030
+ if (node.astroIgnored) {
1031
+ return replaceEndOfLine(options.originalText.slice(node.start, node.end));
1032
+ }
1033
+ if (astroVisitorKeys[node.type])
1034
+ return printAstroNode(path, options, print);
1035
+ if (node.type === 'JSXAttribute') {
1036
+ const attribute = printAttribute(path, options, print);
1037
+ if (attribute)
1038
+ return attribute;
763
1039
  }
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, '>']);
1040
+ if (node.type === 'JSXElement' &&
1041
+ node.closingElement &&
1042
+ node.children.length > 0 &&
1043
+ opensRawSubtree(node)) {
1044
+ const start = node.openingElement.end;
1045
+ const end = node.closingElement.start;
776
1046
  return [
777
- openingTag,
778
- indent([isEmpty ? '' : hardline, formattedSass]),
779
- isEmpty ? '' : hardline,
780
- '</style>',
1047
+ print('openingElement'),
1048
+ replaceEndOfLine(options.originalText.slice(start, end)),
1049
+ print('closingElement'),
781
1050
  ];
782
1051
  }
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;
1052
+ const printed = estree.print(path, options, print, args);
1053
+ if (node.openingFragment?.[synthetic]) {
1054
+ return unwrapFragmentIndent(printed);
791
1055
  }
792
- }
793
- }
1056
+ return printed;
1057
+ },
1058
+ };
794
1059
 
795
- const babelParser = prettierPluginBabel.parsers['babel-ts'];
796
1060
  const languages = [
797
1061
  {
798
1062
  name: 'astro',
@@ -803,30 +1067,14 @@ const languages = [
803
1067
  ];
804
1068
  const parsers = {
805
1069
  astro: {
806
- parse: (source) => parse(source, { position: true }).ast,
1070
+ parse,
807
1071
  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
- },
1072
+ locStart: (node) => node.start,
1073
+ locEnd: (node) => node.end,
823
1074
  },
824
1075
  };
825
1076
  const printers = {
826
- astro: {
827
- print,
828
- embed,
829
- },
1077
+ astro: printer,
830
1078
  };
831
1079
  const defaultOptions = {
832
1080
  tabWidth: 2,