prettier-plugin-astro 0.0.8 → 0.0.12

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/CHANGELOG.md +40 -0
  2. package/README.md +5 -3
  3. package/dist/index.js +872 -0
  4. package/dist/index.js.map +1 -0
  5. package/package.json +29 -11
  6. package/.changeset/README.md +0 -8
  7. package/.changeset/config.json +0 -10
  8. package/.eslintrc +0 -9
  9. package/.github/CODEOWNERS +0 -1
  10. package/.github/ISSUE_TEMPLATE/---01-bug-report.yml +0 -31
  11. package/.github/ISSUE_TEMPLATE/---02-feature-request.yml +0 -50
  12. package/.github/ISSUE_TEMPLATE/---03-documentation.md +0 -7
  13. package/.github/ISSUE_TEMPLATE/config.yml +0 -8
  14. package/.github/PULL_REQUEST_TEMPLATE.md +0 -15
  15. package/.github/dependabot.yml +0 -10
  16. package/.github/workflows/ci.yml +0 -24
  17. package/.github/workflows/format.yml +0 -29
  18. package/.github/workflows/release.yml +0 -52
  19. package/.gitpod.yml +0 -9
  20. package/.prettierignore +0 -1
  21. package/.prettierrc.json +0 -7
  22. package/jsconfig.json +0 -3
  23. package/src/index.d.ts +0 -9
  24. package/src/index.js +0 -38
  25. package/src/options.js +0 -24
  26. package/src/parse.js +0 -8
  27. package/src/printer.js +0 -452
  28. package/src/utils.js +0 -558
  29. package/test/astro-prettier.test.mjs +0 -123
  30. package/test/fixtures/in/attribute-with-embedded-expr.astro +0 -6
  31. package/test/fixtures/in/basic.astro +0 -13
  32. package/test/fixtures/in/converts-to-shorthand.astro +0 -5
  33. package/test/fixtures/in/doctype-with-embedded-expr.astro +0 -26
  34. package/test/fixtures/in/doctype-with-extra-attributes.astro +0 -26
  35. package/test/fixtures/in/embedded-expr.astro +0 -26
  36. package/test/fixtures/in/embedded-in-markdown.md +0 -332
  37. package/test/fixtures/in/expr-and-html-comment.astro +0 -20
  38. package/test/fixtures/in/frontmatter.astro +0 -18
  39. package/test/fixtures/in/html-comment.astro +0 -5
  40. package/test/fixtures/in/maximum-call-stack-size-exceeded.astro +0 -1
  41. package/test/fixtures/in/preserve-tag-case.astro +0 -8
  42. package/test/fixtures/in/single-style-element.astro +0 -9
  43. package/test/fixtures/in/unclosed-tag.astro +0 -42
  44. package/test/fixtures/in/with-codespans.astro +0 -19
  45. package/test/fixtures/in/with-indented-sass.astro +0 -20
  46. package/test/fixtures/in/with-script.astro +0 -8
  47. package/test/fixtures/in/with-scss.astro +0 -24
  48. package/test/fixtures/in/with-styles.astro +0 -21
  49. package/test/fixtures/out/attribute-with-embedded-expr.astro +0 -6
  50. package/test/fixtures/out/basic.astro +0 -12
  51. package/test/fixtures/out/converts-to-shorthand.astro +0 -6
  52. package/test/fixtures/out/doctype-with-embedded-expr.astro +0 -23
  53. package/test/fixtures/out/doctype-with-extra-attributes.astro +0 -23
  54. package/test/fixtures/out/embedded-expr.astro +0 -22
  55. package/test/fixtures/out/embedded-in-markdown.md +0 -336
  56. package/test/fixtures/out/expr-and-html-comment.astro +0 -5
  57. package/test/fixtures/out/frontmatter.astro +0 -16
  58. package/test/fixtures/out/html-comment.astro +0 -5
  59. package/test/fixtures/out/maximum-call-stack-size-exceeded.astro +0 -2
  60. package/test/fixtures/out/preserve-tag-case.astro +0 -8
  61. package/test/fixtures/out/single-style-element.astro +0 -9
  62. package/test/fixtures/out/unclosed-tag.astro +0 -34
  63. package/test/fixtures/out/with-codespans.astro +0 -17
  64. package/test/fixtures/out/with-indented-sass.astro +0 -17
  65. package/test/fixtures/out/with-script.astro +0 -8
  66. package/test/fixtures/out/with-scss.astro +0 -21
  67. package/test/fixtures/out/with-styles.astro +0 -18
  68. package/test/package.json +0 -3
  69. package/test/test-utils.mjs +0 -35
package/src/parse.js DELETED
@@ -1,8 +0,0 @@
1
- const { parse: parseAstro } = require('@astrojs/parser');
2
-
3
- /**
4
- * @param {string} text
5
- */
6
- const parse = (text) => parseAstro(text);
7
-
8
- module.exports = parse;
package/src/printer.js DELETED
@@ -1,452 +0,0 @@
1
- const {
2
- builders: { join, fill, line, literalline, hardline, softline, group, conditionalGroup, breakParent, indent, dedent },
3
- utils: { removeLines },
4
- } = require('prettier/doc');
5
-
6
- const { parseSortOrder } = require('./options');
7
- const {
8
- isASTNode,
9
- isEmptyTextNode,
10
- isPreTagContent,
11
- isInlineElement,
12
- isEmptyDoc,
13
- isTextNodeStartingWithWhitespace,
14
- isTextNodeStartingWithLinebreak,
15
- isTextNodeEndingWithWhitespace,
16
- trimTextNodeLeft,
17
- trimTextNodeRight,
18
- isLoneMustacheTag,
19
- isAttributeShorthand,
20
- isOrCanBeConvertedToShorthand,
21
- selfClosingTags,
22
- formattableAttributes,
23
- getUnencodedText,
24
- replaceEndOfLineWith,
25
- forceIntoExpression,
26
- shouldHugStart,
27
- shouldHugEnd,
28
- canOmitSoftlineBeforeClosingTag,
29
- startsWithLinebreak,
30
- endsWithLinebreak,
31
- printRaw,
32
- trim,
33
- isLine,
34
- trimChildren,
35
- flatten,
36
- getText,
37
- } = require('./utils');
38
-
39
- const supportedStyleLangValues = ['css', 'scss'];
40
-
41
- /**
42
- *
43
- * @param {import('@astrojs/parser').Ast} node
44
- * @param {import('prettier').AstPath<import('@astrojs/parser').Ast>} path
45
- * @param {import('prettier').Parseropts<any>} opts
46
- * @param {(path: import('prettier').AstPath<any>) => import('prettier').Doc} print
47
- */
48
- const printTopLevelParts = (node, path, opts, print) => {
49
- const parts = {
50
- frontmatter: [],
51
- markup: [],
52
- styles: [],
53
- };
54
-
55
- if (node.module) {
56
- parts.frontmatter.push(path.call(print, 'module'));
57
- }
58
-
59
- if (node.css) {
60
- parts.styles.push(path.call(print, 'css'));
61
- }
62
-
63
- if (node.html) {
64
- parts.markup.push(path.call(print, 'html'));
65
- }
66
-
67
- const docs = flatten([parts.frontmatter, ...parseSortOrder(opts.astroSortOrder).map((p) => parts[p])]).filter((doc) => '' !== doc);
68
- return group([join(hardline, docs)]);
69
- };
70
-
71
- const printAttributeNodeValue = (path, print, quotes, node) => {
72
- const valueDocs = path.map((childPath) => childPath.call(print), 'value');
73
-
74
- if (!quotes || !formattableAttributes.includes(node.name)) {
75
- return valueDocs;
76
- } else {
77
- return indent(group(trim(valueDocs, isLine)));
78
- }
79
- };
80
-
81
- function printJS(path, print, name, { forceSingleQuote, forceSingleLine }) {
82
- path.getValue()[name].isJS = true;
83
- path.getValue()[name].forceSingleQuote = forceSingleQuote;
84
- path.getValue()[name].forceSingleLine = forceSingleLine;
85
- return path.call(print, name);
86
- }
87
-
88
- /** @type {import('prettier').Printer['print']} */
89
- const print = (path, opts, print) => {
90
- const node = path.getValue();
91
-
92
- switch (true) {
93
- case !node:
94
- return '';
95
- case typeof node === 'string':
96
- return node;
97
- case Array.isArray(node):
98
- return path.map((childPath) => childPath.call(print));
99
- case isASTNode(node):
100
- return printTopLevelParts(node, path, opts, print);
101
- }
102
-
103
- switch (node.type) {
104
- case 'Fragment': {
105
- const text = getText(node, opts);
106
- if (text.length === 0) {
107
- return '';
108
- }
109
- // If we don't see any JSX expressions, this is just embedded HTML
110
- // and we can skip a bunch of work. Hooray!
111
- const hasInlineComponent = node.children.filter((x) => x.type === 'InlineComponent').length > 0;
112
- if (text.indexOf('{') === -1 && !hasInlineComponent) {
113
- node.__isRawHTML = true;
114
- node.content = text;
115
- return path.call(print);
116
- }
117
-
118
- const children = node.children;
119
-
120
- if (children.length === 0 || children.every(isEmptyTextNode)) {
121
- return '';
122
- }
123
- if (!isPreTagContent(path)) {
124
- trimChildren(node.children, path);
125
- const output = trim(
126
- [path.map(print, 'children')],
127
- (n) =>
128
- isLine(n) ||
129
- (typeof n === 'string' && n.trim() === '') ||
130
- // Because printChildren may append this at the end and
131
- // may hide other lines before it
132
- n === breakParent
133
- );
134
- if (output.every((doc) => isEmptyDoc(doc))) {
135
- return '';
136
- }
137
- return group([...output, hardline]);
138
- } else {
139
- return group(path.map(print, 'children'));
140
- }
141
- }
142
- case 'Text':
143
- if (!isPreTagContent(path)) {
144
- if (isEmptyTextNode(node)) {
145
- const hasWhiteSpace = getUnencodedText(node).trim().length < getUnencodedText(node).length;
146
- const hasOneOrMoreNewlines = /\n/.test(getUnencodedText(node));
147
- const hasTwoOrMoreNewlines = /\n\r?\s*\n\r?/.test(getUnencodedText(node));
148
- if (hasTwoOrMoreNewlines) {
149
- return [hardline, hardline];
150
- }
151
- if (hasOneOrMoreNewlines) {
152
- return hardline;
153
- }
154
- if (hasWhiteSpace) {
155
- return line;
156
- }
157
- return '';
158
- }
159
-
160
- /**
161
- * For non-empty text nodes each sequence of non-whitespace characters (effectively,
162
- * each "word") is joined by a single `line`, which will be rendered as a single space
163
- * until this node's current line is out of room, at which `fill` will break at the
164
- * most convenient instance of `line`.
165
- */
166
- return fill(splitTextToDocs(node));
167
- } else {
168
- const rawText = getUnencodedText(node);
169
- if (path.getParentNode().type === 'Attribute') {
170
- // Direct child of attribute value -> add literallines at end of lines
171
- // so that other things don't break in unexpected places
172
- return replaceEndOfLineWith(rawText, literalline);
173
- }
174
- return rawText;
175
- }
176
- case 'Element':
177
- case 'InlineComponent':
178
- case 'Slot':
179
- case 'SlotTemplate':
180
- case 'Window':
181
- case 'Head':
182
- case 'Title': {
183
- const isEmpty = node.children.every((child) => isEmptyTextNode(child));
184
- const isSelfClosingTag = isEmpty && (node.type !== 'Element' || selfClosingTags.indexOf(node.name) !== -1);
185
- const attributes = path.map((childPath) => childPath.call(print), 'attributes');
186
-
187
- if (isSelfClosingTag) {
188
- return group(['<', node.name, indent(group([...attributes, opts.jsxBracketNewLine ? dedent(line) : ''])), ...[opts.jsxBracketNewLine ? '' : ' ', `/>`]]);
189
- }
190
- try {
191
- if (node.name.toLowerCase() === '!doctype') {
192
- attributesWithLowercaseHTML = attributes.map((attribute) => {
193
- if (attribute[0].type === 'line' && attribute[1].toLowerCase() === 'html') {
194
- attribute[1] = attribute[1].toLowerCase();
195
- return attribute;
196
- }
197
- return attribute;
198
- });
199
-
200
- return group(['<', node.name.toUpperCase(), ...attributesWithLowercaseHTML, `>`]);
201
- }
202
- } catch (e) {
203
- console.warn(`error ${e} in the doctype printing`);
204
- }
205
-
206
- const children = node.children;
207
- const firstChild = children[0];
208
- const lastChild = children[children.length - 1];
209
-
210
- const hugStart = shouldHugStart(node, opts);
211
- const hugEnd = shouldHugEnd(node, opts);
212
-
213
- let body;
214
-
215
- if (isEmpty) {
216
- body =
217
- isInlineElement(path, opts, node) && node.children.length && isTextNodeStartingWithWhitespace(node.children[0]) && !isPreTagContent(path)
218
- ? () => line
219
- : () => (opts.jsxBracketNewLine ? '' : softline);
220
- } else if (isPreTagContent(path)) {
221
- body = () => printRaw(node, opts.originalText);
222
- } else if (isInlineElement(path, opts, node) && !isPreTagContent(path)) {
223
- body = () => path.map(print, 'children');
224
- } else {
225
- body = () => path.map(print, 'children');
226
- }
227
-
228
- const openingTag = ['<', node.name, indent(group([...attributes, hugStart ? '' : opts.jsxBracketNewLine && !isPreTagContent(path) ? dedent(softline) : '']))];
229
-
230
- if (hugStart && hugEnd) {
231
- const huggedContent = [softline, group(['>', body(), `</${node.name}`])];
232
- const omitSoftlineBeforeClosingTag = (isEmpty && opts.jsxBracketNewLine) || canOmitSoftlineBeforeClosingTag(node, path, opts);
233
- return group([...openingTag, isEmpty ? group(huggedContent) : group(indent(huggedContent)), omitSoftlineBeforeClosingTag ? '' : softline, '>']);
234
- }
235
-
236
- // No hugging of content means it's either a block element and/or there's whitespace at the start/end
237
- let noHugSeparatorStart = softline;
238
- let noHugSeparatorEnd = softline;
239
- if (isPreTagContent(path)) {
240
- noHugSeparatorStart = '';
241
- noHugSeparatorEnd = '';
242
- } else {
243
- let didSetEndSeparator = false;
244
-
245
- if (!hugStart && firstChild && firstChild.type === 'Text') {
246
- if (isTextNodeStartingWithLinebreak(firstChild) && firstChild !== lastChild && (!isInlineElement(path, opts, node) || isTextNodeEndingWithWhitespace(lastChild))) {
247
- noHugSeparatorStart = hardline;
248
- noHugSeparatorEnd = hardline;
249
- didSetEndSeparator = true;
250
- } else if (isInlineElement(path, opts, node)) {
251
- noHugSeparatorStart = line;
252
- }
253
- trimTextNodeLeft(firstChild);
254
- }
255
- if (!hugEnd && lastChild && lastChild.type === 'Text') {
256
- if (isInlineElement(path, opts, node) && !didSetEndSeparator) {
257
- noHugSeparatorEnd = line;
258
- }
259
- trimTextNodeRight(lastChild);
260
- }
261
- }
262
-
263
- if (hugStart) {
264
- return group([...openingTag, indent([softline, group(['>', body()])]), noHugSeparatorEnd, `</${node.name}>`]);
265
- }
266
-
267
- if (hugEnd) {
268
- return group([
269
- ...openingTag,
270
- '>',
271
- indent([noHugSeparatorStart, group([body(), `</${node.name}`])]),
272
- canOmitSoftlineBeforeClosingTag(node, path, opts) ? '' : softline,
273
- '>',
274
- ]);
275
- }
276
-
277
- if (isEmpty) {
278
- return group([...openingTag, '>', body(), `</${node.name}>`]);
279
- }
280
-
281
- return group([...openingTag, '>', indent([noHugSeparatorStart, body()]), noHugSeparatorEnd, `</${node.name}>`]);
282
- }
283
- case 'AttributeShorthand': {
284
- return node.expression.name;
285
- }
286
- case 'Attribute': {
287
- if (isOrCanBeConvertedToShorthand(node)) {
288
- return [line, '{', node.name, '}'];
289
- } else {
290
- if (node.value === true) {
291
- return [line, node.name];
292
- }
293
-
294
- const quotes = !isLoneMustacheTag(node.value);
295
- const attrNodeValue = printAttributeNodeValue(path, print, quotes, node);
296
- if (quotes) {
297
- return [line, node.name, '=', '"', attrNodeValue, '"'];
298
- } else {
299
- return [line, node.name, '=', attrNodeValue];
300
- }
301
- }
302
- }
303
- case 'Expression':
304
- return;
305
- case 'MustacheTag':
306
- return [
307
- '{',
308
- printJS(path, print, 'expression', {
309
- forceSingleLine: true,
310
- forceSingleQuote: false,
311
- }),
312
- '}',
313
- ];
314
- case 'Spread':
315
- return [
316
- line,
317
- '{...',
318
- printJS(path, print, 'expression', {
319
- forceSingleQuote: true,
320
- forceSingleLine: false,
321
- }),
322
- '}',
323
- ];
324
- case 'Comment':
325
- return [`<!--`, getUnencodedText(node), `-->`];
326
- case 'CodeSpan':
327
- return getUnencodedText(node);
328
- case 'CodeFence':
329
- console.debug(node);
330
- return getUnencodedText(node);
331
- // We should use `node.metadata` to select a parser to embed with... something like return [node.metadata, hardline textToDoc(node.getMetadataLanguage()), hardline, `\`\`\``];
332
- default: {
333
- throw new Error(`Unhandled node type "${node.type}"!`);
334
- }
335
- }
336
- };
337
-
338
- /**
339
- * Split the text into words separated by whitespace. Replace the whitespaces by lines,
340
- * collapsing multiple whitespaces into a single line.
341
- *
342
- * If the text starts or ends with multiple newlines, two of those should be kept.
343
- */
344
- function splitTextToDocs(node) {
345
- const text = getUnencodedText(node);
346
- let docs = text.split(/[\t\n\f\r ]+/);
347
-
348
- docs = join(line, docs).parts.filter((s) => s !== '');
349
-
350
- if (startsWithLinebreak(text)) {
351
- docs[0] = hardline;
352
- }
353
- if (startsWithLinebreak(text, 2)) {
354
- docs = [hardline, ...docs];
355
- }
356
-
357
- if (endsWithLinebreak(text)) {
358
- docs[docs.length - 1] = hardline;
359
- }
360
- if (endsWithLinebreak(text, 2)) {
361
- docs = [...docs, hardline];
362
- }
363
-
364
- return docs;
365
- }
366
-
367
- function expressionParser(text, parsers, opts) {
368
- const ast = parsers.babel(text, parsers, opts);
369
-
370
- return { ...ast, program: ast.program.body[0].expression };
371
- }
372
-
373
- /** @type {import('prettier').Printer['embed']} */
374
- const embed = (path, print, textToDoc, opts) => {
375
- const node = path.getValue();
376
-
377
- if (!node) return null;
378
-
379
- if (node.isJS) {
380
- try {
381
- const embeddedopts = {
382
- parser: expressionParser,
383
- };
384
- if (node.forceSingleQuote) {
385
- embeddedopts.singleQuote = true;
386
- }
387
-
388
- const docs = textToDoc(forceIntoExpression(getText(node, opts)), embeddedopts);
389
- return node.forceSingleLine ? removeLines(docs) : docs;
390
- } catch (e) {
391
- return getText(node, opts);
392
- }
393
- }
394
-
395
- if (node.type === 'Script' && node.context === 'setup') {
396
- return group(['---', hardline, textToDoc(node.content, { ...opts, parser: 'typescript' }), '---', hardline]);
397
- }
398
-
399
- // format <script type="module"> content
400
- if (node.type === 'Text') {
401
- const parent = path.getParentNode();
402
- if (parent.type === 'Element' && parent.name === 'script') {
403
- const [formatttedScript, _] = textToDoc(node.data, { ...opts, parser: 'typescript' });
404
- return group(formatttedScript);
405
- }
406
- }
407
-
408
- if (node.type === 'Style') {
409
- let styleLang = '';
410
- let parserLang = '';
411
-
412
- if ('attributes' in node) {
413
- const langAttribute = node.attributes.filter((x) => x.name === 'lang');
414
- if (langAttribute.length === 0) styleLang = 'css';
415
- else {
416
- styleLang = langAttribute[0].value[0].raw.toLowerCase();
417
- }
418
- }
419
- if (styleLang in supportedStyleLangValues) parserLang = styleLang;
420
- // TODO(obnoxiousnerd): Provide error handling in case of unrecognized
421
- // styles language.
422
- else parserLang = 'css';
423
-
424
- // the css parser appends an extra indented hardline, which we want outside of the `indent()`,
425
- // so we remove the last element of the array
426
- const [formatttedStyles, _] = textToDoc(node.content.styles, { ...opts, parser: parserLang });
427
- return group([styleLang !== 'css' ? `<style lang="${styleLang}">` : '<style>', indent([hardline, formatttedStyles]), hardline, '</style>', hardline]);
428
- }
429
-
430
- if (node.__isRawHTML) {
431
- return textToDoc(node.content, { ...opts, parser: 'html' });
432
- }
433
-
434
- return null;
435
- };
436
-
437
- /** @type {import('prettier').Printer['hasPrettierIgnore']} */
438
- const hasPrettierIgnore = (path) => {
439
- const node = path.getNode();
440
- const isSimpleIgnore = (comment) =>
441
- comment.value.includes('prettier-ignore') && !comment.value.includes('prettier-ignore-start') && !comment.value.includes('prettier-ignore-end');
442
- return node && node.comments && node.comments.length > 0 && node.comments.some(isSimpleIgnore);
443
- };
444
-
445
- /** @type {import('prettier').Printer} */
446
- const printer = {
447
- print,
448
- embed,
449
- hasPrettierIgnore,
450
- };
451
-
452
- module.exports = printer;