prettier-plugin-astro 0.0.7 → 0.0.11
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/CHANGELOG.md +40 -0
- package/README.md +4 -2
- package/dist/index.js +867 -0
- package/dist/index.js.map +1 -0
- package/package.json +30 -12
- package/.changeset/README.md +0 -8
- package/.changeset/config.json +0 -10
- package/.eslintrc +0 -9
- package/.github/CODEOWNERS +0 -1
- package/.github/ISSUE_TEMPLATE/---01-bug-report.yml +0 -31
- package/.github/ISSUE_TEMPLATE/---02-feature-request.yml +0 -50
- package/.github/ISSUE_TEMPLATE/---03-documentation.md +0 -7
- package/.github/ISSUE_TEMPLATE/config.yml +0 -8
- package/.github/PULL_REQUEST_TEMPLATE.md +0 -15
- package/.github/workflows/ci.yml +0 -26
- package/.github/workflows/format.yml +0 -29
- package/.github/workflows/release.yml +0 -52
- package/.gitpod.yml +0 -9
- package/.prettierignore +0 -1
- package/.prettierrc.json +0 -7
- package/jsconfig.json +0 -3
- package/src/index.d.ts +0 -9
- package/src/index.js +0 -38
- package/src/options.js +0 -24
- package/src/parse.js +0 -8
- package/src/printer.js +0 -442
- package/src/utils.js +0 -558
- package/test/astro-prettier.test.mjs +0 -123
- package/test/fixtures/in/attribute-with-embedded-expr.astro +0 -6
- package/test/fixtures/in/basic.astro +0 -13
- package/test/fixtures/in/converts-to-shorthand.astro +0 -5
- package/test/fixtures/in/doctype-with-embedded-expr.astro +0 -26
- package/test/fixtures/in/doctype-with-extra-attributes.astro +0 -26
- package/test/fixtures/in/embedded-expr.astro +0 -26
- package/test/fixtures/in/embedded-in-markdown.md +0 -332
- package/test/fixtures/in/expr-and-html-comment.astro +0 -20
- package/test/fixtures/in/frontmatter.astro +0 -18
- package/test/fixtures/in/html-comment.astro +0 -5
- package/test/fixtures/in/maximum-call-stack-size-exceeded.astro +0 -1
- package/test/fixtures/in/preserve-tag-case.astro +0 -5
- package/test/fixtures/in/single-style-element.astro +0 -9
- package/test/fixtures/in/unclosed-tag.astro +0 -42
- package/test/fixtures/in/with-codespans.astro +0 -19
- package/test/fixtures/in/with-indented-sass.astro +0 -20
- package/test/fixtures/in/with-script.astro +0 -5
- package/test/fixtures/in/with-scss.astro +0 -24
- package/test/fixtures/in/with-styles.astro +0 -21
- package/test/fixtures/out/attribute-with-embedded-expr.astro +0 -6
- package/test/fixtures/out/basic.astro +0 -12
- package/test/fixtures/out/converts-to-shorthand.astro +0 -6
- package/test/fixtures/out/doctype-with-embedded-expr.astro +0 -23
- package/test/fixtures/out/doctype-with-extra-attributes.astro +0 -23
- package/test/fixtures/out/embedded-expr.astro +0 -22
- package/test/fixtures/out/embedded-in-markdown.md +0 -336
- package/test/fixtures/out/expr-and-html-comment.astro +0 -5
- package/test/fixtures/out/frontmatter.astro +0 -16
- package/test/fixtures/out/html-comment.astro +0 -5
- package/test/fixtures/out/maximum-call-stack-size-exceeded.astro +0 -2
- package/test/fixtures/out/preserve-tag-case.astro +0 -5
- package/test/fixtures/out/single-style-element.astro +0 -9
- package/test/fixtures/out/unclosed-tag.astro +0 -34
- package/test/fixtures/out/with-codespans.astro +0 -17
- package/test/fixtures/out/with-indented-sass.astro +0 -17
- package/test/fixtures/out/with-script.astro +0 -5
- package/test/fixtures/out/with-scss.astro +0 -21
- package/test/fixtures/out/with-styles.astro +0 -18
- package/test/package.json +0 -3
- package/test/test-utils.mjs +0 -35
package/src/utils.js
DELETED
|
@@ -1,558 +0,0 @@
|
|
|
1
|
-
const { doc } = require('prettier');
|
|
2
|
-
|
|
3
|
-
// @see http://xahlee.info/js/html5_non-closing_tag.html
|
|
4
|
-
const selfClosingTags = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'link', 'meta', 'param', 'source', 'track', 'wbr'];
|
|
5
|
-
|
|
6
|
-
// https://developer.mozilla.org/en-US/docs/Web/HTML/Block-level_elements#Elements
|
|
7
|
-
const blockElements = [
|
|
8
|
-
'address',
|
|
9
|
-
'article',
|
|
10
|
-
'aside',
|
|
11
|
-
'blockquote',
|
|
12
|
-
'details',
|
|
13
|
-
'dialog',
|
|
14
|
-
'dd',
|
|
15
|
-
'div',
|
|
16
|
-
'dl',
|
|
17
|
-
'dt',
|
|
18
|
-
'fieldset',
|
|
19
|
-
'figcaption',
|
|
20
|
-
'figure',
|
|
21
|
-
'footer',
|
|
22
|
-
'form',
|
|
23
|
-
'h1',
|
|
24
|
-
'h2',
|
|
25
|
-
'h3',
|
|
26
|
-
'h4',
|
|
27
|
-
'h5',
|
|
28
|
-
'h6',
|
|
29
|
-
'header',
|
|
30
|
-
'hgroup',
|
|
31
|
-
'hr',
|
|
32
|
-
'li',
|
|
33
|
-
'main',
|
|
34
|
-
'nav',
|
|
35
|
-
'ol',
|
|
36
|
-
'p',
|
|
37
|
-
'pre',
|
|
38
|
-
'section',
|
|
39
|
-
'table',
|
|
40
|
-
'ul',
|
|
41
|
-
];
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* HTML attributes that we may safely reformat (trim whitespace, add or remove newlines)
|
|
45
|
-
*/
|
|
46
|
-
const formattableAttributes = [
|
|
47
|
-
// None at the moment
|
|
48
|
-
// Prettier HTML does not format attributes at all
|
|
49
|
-
// and to be consistent we leave this array empty for now
|
|
50
|
-
];
|
|
51
|
-
|
|
52
|
-
const rootNodeKeys = new Set(['html', 'css', 'module']);
|
|
53
|
-
/**
|
|
54
|
-
*
|
|
55
|
-
* @param {any} node
|
|
56
|
-
* @returns {node is import('@astrojs/parser').Ast}
|
|
57
|
-
*/
|
|
58
|
-
const isASTNode = (node) => typeof node === 'object' && Object.keys(node).filter((key) => rootNodeKeys.has(key)).length === rootNodeKeys.size;
|
|
59
|
-
|
|
60
|
-
/**
|
|
61
|
-
*
|
|
62
|
-
* @param {any} node
|
|
63
|
-
* @returns {boolean}
|
|
64
|
-
*/
|
|
65
|
-
const isEmptyTextNode = (node) => {
|
|
66
|
-
return !!node && node.type === 'Text' && getUnencodedText(node).trim() === '';
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
const isPreTagContent = (path) => {
|
|
70
|
-
const stack = path.stack;
|
|
71
|
-
|
|
72
|
-
return stack.some((node) => (node.type === 'Element' && node.name.toLowerCase() === 'pre') || (node.type === 'Attribute' && !formattableAttributes.includes(node.name)));
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
function isLoneMustacheTag(node) {
|
|
76
|
-
return node !== true && node.length === 1 && node[0].type === 'MustacheTag';
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
function isAttributeShorthand(node) {
|
|
80
|
-
return node !== true && node.length === 1 && node[0].type === 'AttributeShorthand';
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* True if node is of type `{a}` or `a={a}`
|
|
85
|
-
*/
|
|
86
|
-
function isOrCanBeConvertedToShorthand(node) {
|
|
87
|
-
if (isAttributeShorthand(node.value)) {
|
|
88
|
-
return true;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (isLoneMustacheTag(node.value)) {
|
|
92
|
-
const expression = node.value[0].expression;
|
|
93
|
-
return (expression.type === 'Identifier' && expression.name === node.name) || (expression.type === 'Expression' && expression.codeChunks[0] === node.name);
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
return false;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const flatten = (arrays) => [].concat.apply([], arrays);
|
|
100
|
-
|
|
101
|
-
/**
|
|
102
|
-
*
|
|
103
|
-
* @param {any} node
|
|
104
|
-
* @param {import('prettier').ParserOptions} options
|
|
105
|
-
*/
|
|
106
|
-
function getText(node, options) {
|
|
107
|
-
const leadingComments = node.leadingComments;
|
|
108
|
-
|
|
109
|
-
return options.originalText.slice(
|
|
110
|
-
options.locStart(
|
|
111
|
-
// if there are comments before the node they are not included
|
|
112
|
-
// in the `start` of the node itself
|
|
113
|
-
(leadingComments && leadingComments[0]) || node
|
|
114
|
-
),
|
|
115
|
-
options.locEnd(node)
|
|
116
|
-
);
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
function getUnencodedText(node) {
|
|
120
|
-
// `raw` will contain HTML entities in unencoded form
|
|
121
|
-
return node.raw || node.data;
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
function replaceEndOfLineWith(text, replacement) {
|
|
125
|
-
const parts = [];
|
|
126
|
-
for (const part of text.split('\n')) {
|
|
127
|
-
if (parts.length > 0) {
|
|
128
|
-
parts.push(replacement);
|
|
129
|
-
}
|
|
130
|
-
if (part.endsWith('\r')) {
|
|
131
|
-
parts.push(part.slice(0, -1));
|
|
132
|
-
} else {
|
|
133
|
-
parts.push(part);
|
|
134
|
-
}
|
|
135
|
-
}
|
|
136
|
-
return parts;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
/**
|
|
140
|
-
*
|
|
141
|
-
* @param { ElementNode | InlineComponentNode | SlotNode | WindowNode | HeadNode | TitleNode | SlotTemplateNode } node
|
|
142
|
-
* @param {string} originalText string
|
|
143
|
-
* @param {boolean} stripLeadingAndTrailingNewline boolean
|
|
144
|
-
* @returns {string}
|
|
145
|
-
*/
|
|
146
|
-
function printRaw(node, originalText, stripLeadingAndTrailingNewline = false) {
|
|
147
|
-
if (node.children.length === 0) {
|
|
148
|
-
return '';
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
const firstChild = node.children[0];
|
|
152
|
-
const lastChild = node.children[node.children.length - 1];
|
|
153
|
-
|
|
154
|
-
let raw = originalText.substring(firstChild.start, lastChild.end);
|
|
155
|
-
|
|
156
|
-
if (!stripLeadingAndTrailingNewline) {
|
|
157
|
-
return raw;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
if (startsWithLinebreak(raw)) {
|
|
161
|
-
raw = raw.substring(raw.indexOf('\n') + 1);
|
|
162
|
-
}
|
|
163
|
-
if (endsWithLinebreak(raw)) {
|
|
164
|
-
raw = raw.substring(0, raw.lastIndexOf('\n'));
|
|
165
|
-
if (raw.charAt(raw.length - 1) === '\r') {
|
|
166
|
-
raw = raw.substring(0, raw.length - 1);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return raw;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function isNodeWithChildren(node) {
|
|
174
|
-
return !!node.children;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
function isInlineElement(path, options, node) {
|
|
178
|
-
return node && node.type === 'Element' && !isBlockElement(node, options) && !isPreTagContent(path);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
function isBlockElement(node, options) {
|
|
182
|
-
return node && node.type === 'Element' && options.htmlWhitespaceSensitivity !== 'strict' && (options.htmlWhitespaceSensitivity === 'ignore' || blockElements.includes(node.name));
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
function isTextNodeStartingWithLinebreak(node, nrLines = 1) {
|
|
186
|
-
return node.type === 'Text' && startsWithLinebreak(getUnencodedText(node), nrLines);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
function startsWithLinebreak(text, nrLines = 1) {
|
|
190
|
-
return new RegExp(`^([\\t\\f\\r ]*\\n){${nrLines}}`).test(text);
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
function isTextNodeEndingWithLinebreak(node, nrLines = 1) {
|
|
194
|
-
return node.type === 'Text' && endsWithLinebreak(getUnencodedText(node), nrLines);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
function endsWithLinebreak(text, nrLines = 1) {
|
|
198
|
-
return new RegExp(`(\\n[\\t\\f\\r ]*){${nrLines}}$`).test(text);
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
function isTextNodeStartingWithWhitespace(node) {
|
|
202
|
-
return node.type === 'Text' && /^\s/.test(getUnencodedText(node));
|
|
203
|
-
}
|
|
204
|
-
|
|
205
|
-
function isTextNodeEndingWithWhitespace(node) {
|
|
206
|
-
return node.type === 'Text' && /\s$/.test(getUnencodedText(node));
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
function forceIntoExpression(statement) {
|
|
210
|
-
// note the trailing newline: if the statement ends in a // comment,
|
|
211
|
-
// we can't add the closing bracket right afterwards
|
|
212
|
-
return `(${statement}\n)`;
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
function shouldHugStart(node, options) {
|
|
216
|
-
if (isBlockElement(node, options)) {
|
|
217
|
-
return false;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (!isNodeWithChildren(node)) {
|
|
221
|
-
return false;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
const children = node.children;
|
|
225
|
-
if (children.length === 0) {
|
|
226
|
-
return true;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
const firstChild = children[0];
|
|
230
|
-
return !isTextNodeStartingWithWhitespace(firstChild);
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
/**
|
|
234
|
-
* Check if given node's end tag should hug its last child. This is the case for inline elements when there's
|
|
235
|
-
* no whitespace between the last child and the `</`.
|
|
236
|
-
*/
|
|
237
|
-
function shouldHugEnd(node, options) {
|
|
238
|
-
if (isBlockElement(node, options)) {
|
|
239
|
-
return false;
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
if (!isNodeWithChildren(node)) {
|
|
243
|
-
return false;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const children = node.children;
|
|
247
|
-
if (children.length === 0) {
|
|
248
|
-
return true;
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
const lastChild = children[children.length - 1];
|
|
252
|
-
return !isTextNodeEndingWithWhitespace(lastChild);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
/**
|
|
256
|
-
* Returns true if the softline between `</tagName` and `>` can be omitted.
|
|
257
|
-
*/
|
|
258
|
-
function canOmitSoftlineBeforeClosingTag(node, path, options) {
|
|
259
|
-
return !options.svelteBracketNewLine && (!hugsStartOfNextNode(node, options) || isLastChildWithinParentBlockElement(path, options));
|
|
260
|
-
}
|
|
261
|
-
|
|
262
|
-
/**
|
|
263
|
-
* Return true if given node does not hug the next node, meaning there's whitespace
|
|
264
|
-
* or the end of the doc afterwards.
|
|
265
|
-
*/
|
|
266
|
-
function hugsStartOfNextNode(node, options) {
|
|
267
|
-
if (node.end === options.originalText.length) {
|
|
268
|
-
// end of document
|
|
269
|
-
return false;
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
return !options.originalText.substring(node.end).match(/^\s/);
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
function getChildren(node) {
|
|
276
|
-
return isNodeWithChildren(node) ? node.children : [];
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
function isLastChildWithinParentBlockElement(path, options) {
|
|
280
|
-
const parent = path.getParentNode();
|
|
281
|
-
if (!parent || !isBlockElement(parent, options)) {
|
|
282
|
-
return false;
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
const children = getChildren(parent);
|
|
286
|
-
const lastChild = children[children.length - 1];
|
|
287
|
-
return lastChild === path.getNode();
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
function trimTextNodeLeft(node) {
|
|
291
|
-
node.raw = node.raw && node.raw.trimLeft();
|
|
292
|
-
node.data = node.data && node.data.trimLeft();
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
function trimTextNodeRight(node) {
|
|
296
|
-
node.raw = node.raw && node.raw.trimRight();
|
|
297
|
-
node.data = node.data && node.data.trimRight();
|
|
298
|
-
}
|
|
299
|
-
|
|
300
|
-
function findLastIndex(isMatch, items) {
|
|
301
|
-
for (let i = items.length - 1; i >= 0; i--) {
|
|
302
|
-
if (isMatch(items[i], i)) {
|
|
303
|
-
return i;
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
|
|
307
|
-
return -1;
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Remove all leading whitespace up until the first non-empty text node,
|
|
312
|
-
* and all trailing whitepsace from the last non-empty text node onwards.
|
|
313
|
-
*/
|
|
314
|
-
function trimChildren(children, path) {
|
|
315
|
-
let firstNonEmptyNode = children.findIndex((n) => !isEmptyTextNode(n) && !doesEmbedStartAfterNode(n, path));
|
|
316
|
-
firstNonEmptyNode = firstNonEmptyNode === -1 ? children.length - 1 : firstNonEmptyNode;
|
|
317
|
-
|
|
318
|
-
let lastNonEmptyNode = findLastIndex((n, idx) => {
|
|
319
|
-
// Last node is ok to end at the start of an embedded region,
|
|
320
|
-
// if it's not a comment (which should stick to the region)
|
|
321
|
-
return !isEmptyTextNode(n) && ((idx === children.length - 1 && n.type !== 'Comment') || !doesEmbedStartAfterNode(n, path));
|
|
322
|
-
}, children);
|
|
323
|
-
lastNonEmptyNode = lastNonEmptyNode === -1 ? 0 : lastNonEmptyNode;
|
|
324
|
-
|
|
325
|
-
for (let i = 0; i <= firstNonEmptyNode; i++) {
|
|
326
|
-
const n = children[i];
|
|
327
|
-
if (n.type === 'Text') {
|
|
328
|
-
trimTextNodeLeft(n);
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
for (let i = children.length - 1; i >= lastNonEmptyNode; i--) {
|
|
333
|
-
const n = children[i];
|
|
334
|
-
if (n.type === 'Text') {
|
|
335
|
-
trimTextNodeRight(n);
|
|
336
|
-
}
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* Returns siblings, that is, the children of the parent.
|
|
342
|
-
*/
|
|
343
|
-
function getSiblings(path) {
|
|
344
|
-
let parent = path.getParentNode();
|
|
345
|
-
|
|
346
|
-
if (isASTNode(parent)) {
|
|
347
|
-
parent = parent.html;
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
return getChildren(parent);
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
/**
|
|
354
|
-
* Did there use to be any embedded object (that has been snipped out of the AST to be moved)
|
|
355
|
-
* at the specified position?
|
|
356
|
-
*/
|
|
357
|
-
function doesEmbedStartAfterNode(node, path, siblings = getSiblings(path)) {
|
|
358
|
-
// If node is not at the top level of html, an embed cannot start after it,
|
|
359
|
-
// because embeds are only at the top level
|
|
360
|
-
if (!isNodeTopLevelHTML(node, path)) {
|
|
361
|
-
return false;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
const position = node.end;
|
|
365
|
-
const root = path.stack[0];
|
|
366
|
-
|
|
367
|
-
const embeds = [root.module, root.html, root.css];
|
|
368
|
-
|
|
369
|
-
const nextNode = siblings[siblings.indexOf(node) + 1];
|
|
370
|
-
return embeds.find((n) => n && n.start >= position && (!nextNode || n.end <= nextNode.start));
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
function isNodeTopLevelHTML(node, path) {
|
|
374
|
-
const root = path.stack[0];
|
|
375
|
-
return !!root.html && !!root.html.children && root.html.children.includes(node);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
/**
|
|
379
|
-
* Check if doc is a hardline.
|
|
380
|
-
* We can't just rely on a simple equality check because the doc could be created with another
|
|
381
|
-
* runtime version of prettier than what we import, making a reference check fail.
|
|
382
|
-
*/
|
|
383
|
-
function isHardline(docToCheck) {
|
|
384
|
-
return docToCheck === doc.builders.hardline || deepEqual(docToCheck, doc.builders.hardline);
|
|
385
|
-
}
|
|
386
|
-
|
|
387
|
-
/**
|
|
388
|
-
* Simple deep equal function which suits our needs. Only works properly on POJOs without cyclic deps.
|
|
389
|
-
*/
|
|
390
|
-
function deepEqual(x, y) {
|
|
391
|
-
if (x === y) {
|
|
392
|
-
return true;
|
|
393
|
-
} else if (typeof x == 'object' && x != null && typeof y == 'object' && y != null) {
|
|
394
|
-
if (Object.keys(x).length != Object.keys(y).length) return false;
|
|
395
|
-
|
|
396
|
-
for (var prop in x) {
|
|
397
|
-
if (y.hasOwnProperty(prop)) {
|
|
398
|
-
if (!deepEqual(x[prop], y[prop])) return false;
|
|
399
|
-
} else {
|
|
400
|
-
return false;
|
|
401
|
-
}
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
return true;
|
|
405
|
-
} else {
|
|
406
|
-
return false;
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
|
|
410
|
-
function isLine(docToCheck) {
|
|
411
|
-
return (
|
|
412
|
-
isHardline(docToCheck) ||
|
|
413
|
-
(typeof docToCheck === 'object' && docToCheck.type === 'line') ||
|
|
414
|
-
(typeof docToCheck === 'object' && docToCheck.type === 'concat' && docToCheck.parts.every(isLine))
|
|
415
|
-
);
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
/**
|
|
419
|
-
* Check if the doc is empty, i.e. consists of nothing more than empty strings (possibly nested).
|
|
420
|
-
*/
|
|
421
|
-
function isEmptyDoc(doc) {
|
|
422
|
-
if (typeof doc === 'string') {
|
|
423
|
-
return doc.length === 0;
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (doc.type === 'line') {
|
|
427
|
-
return !doc.keepIfLonely;
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
// Since Prettier 2.3.0, concats are represented as flat arrays
|
|
431
|
-
if (Array.isArray(doc)) {
|
|
432
|
-
return doc.length === 0;
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
const { contents } = doc;
|
|
436
|
-
|
|
437
|
-
if (contents) {
|
|
438
|
-
return isEmptyDoc(contents);
|
|
439
|
-
}
|
|
440
|
-
|
|
441
|
-
const { parts } = doc;
|
|
442
|
-
|
|
443
|
-
if (parts) {
|
|
444
|
-
return isEmptyGroup(parts);
|
|
445
|
-
}
|
|
446
|
-
|
|
447
|
-
return false;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
function isEmptyGroup(group) {
|
|
451
|
-
return !group.find((doc) => !isEmptyDoc(doc));
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
/**
|
|
455
|
-
* Trims both leading and trailing nodes matching `isWhitespace` independent of nesting level
|
|
456
|
-
* (though all trimmed adjacent nodes need to be a the same level). Modifies the `docs` array.
|
|
457
|
-
*/
|
|
458
|
-
function trim(docs, isWhitespace) {
|
|
459
|
-
trimLeft(docs, isWhitespace);
|
|
460
|
-
trimRight(docs, isWhitespace);
|
|
461
|
-
|
|
462
|
-
return docs;
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
/**
|
|
466
|
-
* Trims the leading nodes matching `isWhitespace` independent of nesting level (though all nodes need to be a the same level).
|
|
467
|
-
* If there are empty docs before the first whitespace, they are removed, too.
|
|
468
|
-
*/
|
|
469
|
-
function trimLeft(group, isWhitespace) {
|
|
470
|
-
let firstNonWhitespace = group.findIndex((doc) => !isEmptyDoc(doc) && !isWhitespace(doc));
|
|
471
|
-
|
|
472
|
-
if (firstNonWhitespace < 0 && group.length) {
|
|
473
|
-
firstNonWhitespace = group.length;
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
if (firstNonWhitespace > 0) {
|
|
477
|
-
const removed = group.splice(0, firstNonWhitespace);
|
|
478
|
-
if (removed.every(isEmptyDoc)) {
|
|
479
|
-
return trimLeft(group, isWhitespace);
|
|
480
|
-
}
|
|
481
|
-
} else {
|
|
482
|
-
const parts = getParts(group[0]);
|
|
483
|
-
|
|
484
|
-
if (parts) {
|
|
485
|
-
return trimLeft(parts, isWhitespace);
|
|
486
|
-
}
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
/**
|
|
491
|
-
* Trims the trailing nodes matching `isWhitespace` independent of nesting level (though all nodes need to be a the same level).
|
|
492
|
-
* If there are empty docs after the last whitespace, they are removed, too.
|
|
493
|
-
*/
|
|
494
|
-
function trimRight(group, isWhitespace) {
|
|
495
|
-
let lastNonWhitespace = group.length ? findLastIndex((doc) => !isEmptyDoc(doc) && !isWhitespace(doc), group) : 0;
|
|
496
|
-
|
|
497
|
-
if (lastNonWhitespace < group.length - 1) {
|
|
498
|
-
const removed = group.splice(lastNonWhitespace + 1);
|
|
499
|
-
if (removed.every(isEmptyDoc)) {
|
|
500
|
-
return trimRight(group, isWhitespace);
|
|
501
|
-
}
|
|
502
|
-
} else {
|
|
503
|
-
const parts = getParts(group[group.length - 1]);
|
|
504
|
-
|
|
505
|
-
if (parts) {
|
|
506
|
-
return trimRight(parts, isWhitespace);
|
|
507
|
-
}
|
|
508
|
-
}
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
function getParts(doc) {
|
|
512
|
-
if (typeof doc === 'object') {
|
|
513
|
-
// Since Prettier 2.3.0, concats are represented as flat arrays
|
|
514
|
-
if (Array.isArray(doc)) {
|
|
515
|
-
return doc;
|
|
516
|
-
}
|
|
517
|
-
if (doc.type === 'fill' || doc.type === 'concat') {
|
|
518
|
-
return doc.parts;
|
|
519
|
-
}
|
|
520
|
-
if (doc.type === 'group') {
|
|
521
|
-
return getParts(doc.contents);
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
}
|
|
525
|
-
|
|
526
|
-
module.exports = {
|
|
527
|
-
isASTNode,
|
|
528
|
-
isEmptyTextNode,
|
|
529
|
-
isPreTagContent,
|
|
530
|
-
isLoneMustacheTag,
|
|
531
|
-
isAttributeShorthand,
|
|
532
|
-
isOrCanBeConvertedToShorthand,
|
|
533
|
-
replaceEndOfLineWith,
|
|
534
|
-
getUnencodedText,
|
|
535
|
-
selfClosingTags,
|
|
536
|
-
formattableAttributes,
|
|
537
|
-
forceIntoExpression,
|
|
538
|
-
isInlineElement,
|
|
539
|
-
isBlockElement,
|
|
540
|
-
isTextNodeStartingWithWhitespace,
|
|
541
|
-
isTextNodeEndingWithWhitespace,
|
|
542
|
-
isTextNodeStartingWithLinebreak,
|
|
543
|
-
startsWithLinebreak,
|
|
544
|
-
endsWithLinebreak,
|
|
545
|
-
isTextNodeEndingWithLinebreak,
|
|
546
|
-
canOmitSoftlineBeforeClosingTag,
|
|
547
|
-
shouldHugStart,
|
|
548
|
-
shouldHugEnd,
|
|
549
|
-
trimTextNodeLeft,
|
|
550
|
-
trimTextNodeRight,
|
|
551
|
-
trimChildren,
|
|
552
|
-
flatten,
|
|
553
|
-
printRaw,
|
|
554
|
-
getText,
|
|
555
|
-
trim,
|
|
556
|
-
isLine,
|
|
557
|
-
isEmptyDoc,
|
|
558
|
-
};
|
|
@@ -1,123 +0,0 @@
|
|
|
1
|
-
import test from 'ava';
|
|
2
|
-
import { format, markdownFormat } from './test-utils.mjs';
|
|
3
|
-
import { promises as fs } from 'fs';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
|
|
6
|
-
const readFile = (path) => fs.readFile(fileURLToPath(new URL(`./fixtures${path}`, import.meta.url))).then((res) => res.toString().replace(/\r\n/g, '\n'));
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Utility to get `[src, out]` files
|
|
10
|
-
* @param name {string}
|
|
11
|
-
* @param ctx {any}
|
|
12
|
-
*/
|
|
13
|
-
const getFiles = async (name) => {
|
|
14
|
-
const [src, out] = await Promise.all([readFile(`/in/${name}.astro`), readFile(`/out/${name}.astro`)]);
|
|
15
|
-
return [src, out];
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
const getMarkdownFiles = async (name) => {
|
|
19
|
-
const [src, out] = await Promise.all([readFile(`/in/${name}.md`), readFile(`/out/${name}.md`)]);
|
|
20
|
-
return [src, out];
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
/**
|
|
24
|
-
* Macro for testing fixtures
|
|
25
|
-
* @param t {TestInterface<unknown>}
|
|
26
|
-
* @param name {string}
|
|
27
|
-
*/
|
|
28
|
-
const Prettier = async (t, name) => {
|
|
29
|
-
const [src, out] = await getFiles(name);
|
|
30
|
-
t.not(src, out);
|
|
31
|
-
|
|
32
|
-
const formatted = format(src);
|
|
33
|
-
t.is(formatted, out);
|
|
34
|
-
// test that our formatting is idempotent
|
|
35
|
-
const formattedTwice = format(formatted);
|
|
36
|
-
t.is(formatted, formattedTwice);
|
|
37
|
-
};
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* Macro title function for nice formatting
|
|
41
|
-
* @param title {string}
|
|
42
|
-
* @param name {string}
|
|
43
|
-
* @returns {string}
|
|
44
|
-
*/
|
|
45
|
-
Prettier.title = (title, name) => `${title}:
|
|
46
|
-
|
|
47
|
-
- input: fixtures/in/${name}.astro
|
|
48
|
-
- output: fixtures/out/${name}.astro`;
|
|
49
|
-
|
|
50
|
-
const PrettierUnaltered = async (t, name) => {
|
|
51
|
-
const [src, out] = await getFiles(name);
|
|
52
|
-
t.is(src, out); // the output should be unchanged
|
|
53
|
-
|
|
54
|
-
const formatted = format(src);
|
|
55
|
-
t.is(formatted, out);
|
|
56
|
-
// test that our formatting is idempotent
|
|
57
|
-
const formattedTwice = format(formatted);
|
|
58
|
-
t.is(formatted, formattedTwice);
|
|
59
|
-
};
|
|
60
|
-
|
|
61
|
-
PrettierUnaltered.title = Prettier.title;
|
|
62
|
-
|
|
63
|
-
const PrettierMarkdown = async (t, name) => {
|
|
64
|
-
const [src, out] = await getMarkdownFiles(name);
|
|
65
|
-
t.not(src, out);
|
|
66
|
-
|
|
67
|
-
const formatted = markdownFormat(src);
|
|
68
|
-
t.not(formatted, out);
|
|
69
|
-
// test that our formatting is idempotent
|
|
70
|
-
const formattedTwice = markdownFormat(formatted);
|
|
71
|
-
t.is(formatted, formattedTwice);
|
|
72
|
-
};
|
|
73
|
-
|
|
74
|
-
PrettierMarkdown.title = (title, name) => `${title}:
|
|
75
|
-
|
|
76
|
-
- input: fixtures/in/${name}.md
|
|
77
|
-
- output: fixtures/out/${name}.md`;
|
|
78
|
-
|
|
79
|
-
test('can format a basic Astro file', Prettier, 'basic');
|
|
80
|
-
|
|
81
|
-
test('can format an Astro file with a single style element', Prettier, 'single-style-element');
|
|
82
|
-
|
|
83
|
-
test('can format a basic Astro file with styles', Prettier, 'with-styles');
|
|
84
|
-
|
|
85
|
-
test('can format a basic Astro file with SCSS styles', Prettier, 'with-scss');
|
|
86
|
-
|
|
87
|
-
test.failing('can format a basic Astro file with styles written in Indented SASS ', Prettier, 'with-indented-sass');
|
|
88
|
-
|
|
89
|
-
test('can format an Astro file with frontmatter', Prettier, 'frontmatter');
|
|
90
|
-
|
|
91
|
-
test('can format an Astro file with embedded JSX expressions', Prettier, 'embedded-expr');
|
|
92
|
-
|
|
93
|
-
test('can format an Astro file with a `<!DOCTYPE html>` + embedded JSX expressions', Prettier, 'doctype-with-embedded-expr');
|
|
94
|
-
|
|
95
|
-
test.failing('can format an Astro file with `<!DOCTYPE>` with extraneous attributes', Prettier, 'doctype-with-extra-attributes');
|
|
96
|
-
|
|
97
|
-
test('can format an Astro file with a JSX expression in an attribute', Prettier, 'attribute-with-embedded-expr');
|
|
98
|
-
|
|
99
|
-
test('does not alter html comments', PrettierUnaltered, 'html-comment');
|
|
100
|
-
|
|
101
|
-
test.todo("properly follow prettier' advice on formatting comments");
|
|
102
|
-
|
|
103
|
-
test('can format an Astro file with a JSX expression and an HTML Comment', Prettier, 'expr-and-html-comment');
|
|
104
|
-
|
|
105
|
-
test('can format an Astro file containing an Astro file embedded in a codeblock', PrettierMarkdown, 'embedded-in-markdown');
|
|
106
|
-
|
|
107
|
-
test('converts valid shorthand variables into shorthand', Prettier, 'converts-to-shorthand');
|
|
108
|
-
|
|
109
|
-
test.failing('an Astro file with an invalidly unclosed tag is still formatted', Prettier, 'unclosed-tag');
|
|
110
|
-
|
|
111
|
-
test.todo('test whether invalid files provide helpful support messages / still try to be parsed by prettier?');
|
|
112
|
-
|
|
113
|
-
test.failing('can format an Astro file with components that are the uppercase version of html elements', PrettierUnaltered, 'preserve-tag-case');
|
|
114
|
-
|
|
115
|
-
test.failing('Autocloses open tags? BUG: RangeError { message: "Maximum call stack size exceeded" }', Prettier, 'maximum-call-size-exceeded');
|
|
116
|
-
|
|
117
|
-
test('can format an Astro file with a script tag inside it', Prettier, 'with-script');
|
|
118
|
-
|
|
119
|
-
test.todo('Can format an Astro file with a HTML style prettier ignore comment: https://prettier.io/docs/en/ignore.html');
|
|
120
|
-
|
|
121
|
-
test.todo('Can format an Astro file with a JS style prettier ignore comment: https://prettier.io/docs/en/ignore.html');
|
|
122
|
-
|
|
123
|
-
test.failing(`Can format an Astro file with a template string`, Prettier, 'with-codespans');
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<!DOCTYPE html>
|
|
2
|
-
<html lang="en">
|
|
3
|
-
<head>
|
|
4
|
-
<meta charset="UTF-8">
|
|
5
|
-
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
|
-
<title>Document</title>
|
|
8
|
-
</head>
|
|
9
|
-
<body>
|
|
10
|
-
<h1>
|
|
11
|
-
Hello world!</h1>
|
|
12
|
-
</body>
|
|
13
|
-
</html>
|