Skip to content

Commit 19654d5

Browse files
dummdidummcarmanchris31Rolaka
authored
(breaking) update to prettier v3 (#335)
Removes concat, applies some needed changes to the print/embed machinery --------- Co-authored-by: Simon H <5968653+dummdidumm@users.noreply.github.com> Co-authored-by: Christopher Carman <carmanchris31@gmail.com> Co-authored-by: Rolaka <hyperion.neil@gmail.com>
1 parent 57bd5e3 commit 19654d5

14 files changed

+531
-3411
lines changed

.github/workflows/main.yml

+2-2
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ jobs:
66
runs-on: ubuntu-latest
77

88
steps:
9-
- uses: actions/checkout@v2
10-
- uses: actions/setup-node@v1
9+
- uses: actions/checkout@v3
10+
- uses: actions/setup-node@v3
1111
with:
1212
cache: npm
1313

CHANGELOG.md

+5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,10 @@
11
# prettier-plugin-svelte changelog
22

3+
## 3.0.0 (Unreleased)
4+
5+
- (breaking) requires `prettier` version 3
6+
- (breaking) requires node version 14 or higher
7+
38
## 2.10.1
49

510
- (chore) mark as compatible with Svelte 4

package-lock.json

+121-3,003
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

+6-8
Original file line numberDiff line numberDiff line change
@@ -28,22 +28,20 @@
2828
},
2929
"homepage": "https://github.com/sveltejs/prettier-plugin-svelte#readme",
3030
"devDependencies": {
31-
"@prettier/plugin-pug": "^1.16.0",
3231
"@rollup/plugin-commonjs": "14.0.0",
3332
"@rollup/plugin-node-resolve": "11.0.1",
34-
"@types/node": "^10.12.18",
35-
"@types/prettier": "^2.4.1",
33+
"@types/node": "^14.0.0",
3634
"ava": "3.15.0",
37-
"prettier": "^2.7.1",
35+
"prettier": "^3.0.0",
3836
"rollup": "2.36.0",
3937
"rollup-plugin-typescript": "1.0.1",
4038
"svelte": "^3.57.0",
41-
"ts-node": "^9.1.1",
42-
"tslib": "^2.0.3",
43-
"typescript": "4.1.3"
39+
"ts-node": "^10.1.1",
40+
"tslib": "^2.6.0",
41+
"typescript": "5.1.3"
4442
},
4543
"peerDependencies": {
46-
"prettier": "^1.16.4 || ^2.0.0",
44+
"prettier": "^3.0.0",
4745
"svelte": "^3.2.0 || ^4.0.0-next.0"
4846
}
4947
}

src/embed.ts

+159-64
Original file line numberDiff line numberDiff line change
@@ -1,77 +1,158 @@
1-
import { Doc, doc, FastPath, ParserOptions } from 'prettier';
1+
import { Doc, doc, FastPath, Options } from 'prettier';
22
import { getText } from './lib/getText';
33
import { snippedTagContentAttribute } from './lib/snipTagContent';
4-
import { isBracketSameLine } from './options';
4+
import { isBracketSameLine, ParserOptions } from './options';
55
import { PrintFn } from './print';
66
import { isLine, removeParentheses, trimRight } from './print/doc-helpers';
7-
import { groupConcat, printWithPrependedAttributeLine } from './print/helpers';
7+
import { isASTNode, printWithPrependedAttributeLine } from './print/helpers';
88
import {
9+
assignCommentsToNodes,
910
getAttributeTextValue,
1011
getLeadingComment,
1112
isIgnoreDirective,
13+
isInsideQuotedAttribute,
1214
isNodeSupportedLanguage,
1315
isPugTemplate,
1416
isTypeScript,
1517
printRaw,
1618
} from './print/node-helpers';
1719
import { CommentNode, ElementNode, Node, ScriptNode, StyleNode } from './print/nodes';
20+
import { extractAttributes } from './lib/extractAttributes';
1821

1922
const {
20-
builders: { concat, hardline, softline, indent, dedent, literalline },
23+
builders: { group, hardline, softline, indent, dedent, literalline },
2124
utils: { removeLines },
2225
} = doc;
2326

24-
export function embed(
25-
path: FastPath,
26-
print: PrintFn,
27-
textToDoc: (text: string, options: object) => Doc,
28-
options: ParserOptions,
29-
): Doc | null {
27+
// Embed works like this in Prettier v3:
28+
// - do depth first traversal of all node properties
29+
// - deepest property is calling embed first
30+
// - if embed returns a function, it will be called after the traversal in a second pass, in the same order (deepest first)
31+
// For performance reasons we try to only return functions when we're sure we need to transform something.
32+
export function embed(path: FastPath, _options: Options) {
3033
const node: Node = path.getNode();
34+
const options = _options as ParserOptions;
35+
if (!options.locStart || !options.locEnd || !options.originalText) {
36+
throw new Error('Missing required options');
37+
}
38+
39+
if (isASTNode(node)) {
40+
assignCommentsToNodes(node);
41+
if (node.module) {
42+
node.module.type = 'Script';
43+
node.module.attributes = extractAttributes(getText(node.module, options));
44+
}
45+
if (node.instance) {
46+
node.instance.type = 'Script';
47+
node.instance.attributes = extractAttributes(getText(node.instance, options));
48+
}
49+
if (node.css) {
50+
node.css.type = 'Style';
51+
node.css.content.type = 'StyleProgram';
52+
}
53+
return null;
54+
}
55+
56+
// embed does depth first traversal with deepest node called first, therefore we need to
57+
// check the parent to see if we are inside an expression that should be embedded.
58+
const parent = path.getParentNode();
59+
const printJsExpression = () =>
60+
(parent as any).expression
61+
? printJS(parent, options.svelteStrictMode ?? false, false, false, 'expression')
62+
: undefined;
63+
const printSvelteBlockJS = (name: string) => printJS(parent, false, true, false, name);
64+
65+
switch (parent.type) {
66+
case 'IfBlock':
67+
case 'ElseBlock':
68+
case 'AwaitBlock':
69+
case 'KeyBlock':
70+
printSvelteBlockJS('expression');
71+
break;
72+
case 'EachBlock':
73+
printSvelteBlockJS('expression');
74+
printSvelteBlockJS('key');
75+
break;
76+
case 'Element':
77+
printJS(parent, options.svelteStrictMode ?? false, false, false, 'tag');
78+
break;
79+
case 'MustacheTag':
80+
printJS(parent, isInsideQuotedAttribute(path, options), false, false, 'expression');
81+
break;
82+
case 'RawMustacheTag':
83+
printJS(parent, false, false, false, 'expression');
84+
break;
85+
case 'Spread':
86+
printJS(parent, false, false, false, 'expression');
87+
break;
88+
case 'ConstTag':
89+
printJS(parent, false, false, true, 'expression');
90+
break;
91+
case 'EventHandler':
92+
case 'Binding':
93+
case 'Class':
94+
case 'Let':
95+
case 'Transition':
96+
case 'Action':
97+
case 'Animation':
98+
case 'InlineComponent':
99+
printJsExpression();
100+
break;
101+
}
31102

32103
if (node.isJS) {
33-
try {
34-
const embeddedOptions: any = {
35-
parser: expressionParser,
36-
};
37-
if (node.forceSingleQuote) {
38-
embeddedOptions.singleQuote = true;
39-
}
104+
return async (
105+
textToDoc: (text: string, options: Options) => Promise<Doc>,
106+
): Promise<Doc> => {
107+
try {
108+
const embeddedOptions = {
109+
// Prettier only allows string references as parsers from v3 onwards,
110+
// so we need to have another public parser and defer to that
111+
parser: 'svelteExpressionParser',
112+
singleQuote: node.forceSingleQuote ? true : options.singleQuote,
113+
};
40114

41-
let docs = textToDoc(
42-
forceIntoExpression(
43-
// If we have snipped content, it was done wrongly and we need to unsnip it.
44-
// This happens for example for {@html `<script>{foo}</script>`}
45-
getText(node, options, true),
46-
),
47-
embeddedOptions,
48-
);
49-
if (node.forceSingleLine) {
50-
docs = removeLines(docs);
51-
}
52-
if (node.removeParentheses) {
53-
docs = removeParentheses(docs);
115+
let docs = await textToDoc(
116+
forceIntoExpression(
117+
// If we have snipped content, it was done wrongly and we need to unsnip it.
118+
// This happens for example for {@html `<script>{foo}</script>`}
119+
getText(node, options, true),
120+
),
121+
embeddedOptions,
122+
);
123+
if (node.forceSingleLine) {
124+
docs = removeLines(docs);
125+
}
126+
if (node.removeParentheses) {
127+
docs = removeParentheses(docs);
128+
}
129+
return docs;
130+
} catch (e) {
131+
return getText(node, options, true);
54132
}
55-
return docs;
56-
} catch (e) {
57-
return getText(node, options, true);
58-
}
133+
};
59134
}
60135

61136
const embedType = (
62137
tag: 'script' | 'style' | 'template',
63138
parser: 'typescript' | 'babel-ts' | 'css' | 'pug',
64139
isTopLevel: boolean,
65-
) =>
66-
embedTag(
67-
tag,
68-
options.originalText,
69-
path,
70-
(content) => formatBodyContent(content, parser, textToDoc, options),
71-
print,
72-
isTopLevel,
73-
options,
74-
);
140+
) => {
141+
return async (
142+
textToDoc: (text: string, options: Options) => Promise<Doc>,
143+
print: PrintFn,
144+
): Promise<Doc> => {
145+
return embedTag(
146+
tag,
147+
options.originalText,
148+
path,
149+
(content) => formatBodyContent(content, parser, textToDoc, options),
150+
print,
151+
isTopLevel,
152+
options,
153+
);
154+
};
155+
};
75156

76157
const embedScript = (isTopLevel: boolean) =>
77158
embedType(
@@ -111,19 +192,17 @@ function forceIntoExpression(statement: string) {
111192
return `(${statement}\n)`;
112193
}
113194

114-
function expressionParser(text: string, parsers: any, options: any) {
115-
const ast = parsers.babel(text, parsers, options);
116-
117-
return { ...ast, program: ast.program.body[0].expression };
118-
}
119-
120195
function preformattedBody(str: string): Doc {
196+
if (!str) {
197+
return '';
198+
}
199+
121200
const firstNewline = /^[\t\f\r ]*\n/;
122201
const lastNewline = /\n[\t\f\r ]*$/;
123202

124203
// If we do not start with a new line prettier might try to break the opening tag
125204
// to keep it together with the string. Use a literal line to skip indentation.
126-
return concat([literalline, str.replace(firstNewline, '').replace(lastNewline, ''), hardline]);
205+
return [literalline, str.replace(firstNewline, '').replace(lastNewline, ''), hardline];
127206
}
128207

129208
function getSnippedContent(node: Node) {
@@ -136,14 +215,14 @@ function getSnippedContent(node: Node) {
136215
}
137216
}
138217

139-
function formatBodyContent(
218+
async function formatBodyContent(
140219
content: string,
141220
parser: 'typescript' | 'babel-ts' | 'css' | 'pug',
142-
textToDoc: (text: string, options: object) => Doc,
221+
textToDoc: (text: string, options: object) => Promise<Doc>,
143222
options: ParserOptions & { pugTabWidth?: number },
144223
) {
145224
try {
146-
const body = textToDoc(content, { parser });
225+
const body = await textToDoc(content, { parser });
147226

148227
if (parser === 'pug' && typeof body === 'string') {
149228
// Pug returns no docs but a final string.
@@ -159,13 +238,13 @@ function formatBodyContent(
159238
.split('\n')
160239
.map((line) => (line ? whitespace + line : line))
161240
.join('\n');
162-
return concat([hardline, pugBody]);
241+
return [hardline, pugBody];
163242
}
164243

165244
const indentIfDesired = (doc: Doc) =>
166245
options.svelteIndentScriptAndStyle ? indent(doc) : doc;
167246
trimRight([body], isLine);
168-
return concat([indentIfDesired(concat([hardline, body])), hardline]);
247+
return [indentIfDesired([hardline, body]), hardline];
169248
} catch (error) {
170249
if (process.env.PRETTIER_DEBUG) {
171250
throw error;
@@ -181,11 +260,11 @@ function formatBodyContent(
181260
}
182261
}
183262

184-
function embedTag(
263+
async function embedTag(
185264
tag: 'script' | 'style' | 'template',
186265
text: string,
187266
path: FastPath,
188-
formatBodyContent: (content: string) => Doc,
267+
formatBodyContent: (content: string) => Promise<Doc>,
189268
print: PrintFn,
190269
isTopLevel: boolean,
191270
options: ParserOptions,
@@ -209,24 +288,24 @@ function embedTag(
209288
));
210289
const body: Doc = canFormat
211290
? content.trim() !== ''
212-
? formatBodyContent(content)
291+
? await formatBodyContent(content)
213292
: content === ''
214293
? ''
215294
: hardline
216295
: preformattedBody(content);
217296

218-
const openingTag = groupConcat([
297+
const openingTag = group([
219298
'<',
220299
tag,
221300
indent(
222-
groupConcat([
301+
group([
223302
...path.map(printWithPrependedAttributeLine(node, options, print), 'attributes'),
224303
isBracketSameLine(options) ? '' : dedent(softline),
225304
]),
226305
),
227306
'>',
228307
]);
229-
let result = groupConcat([openingTag, body, '</', tag, '>']);
308+
let result: Doc = group([openingTag, body, '</', tag, '>']);
230309

231310
const comments = [];
232311
for (const comment of previousComments) {
@@ -241,8 +320,24 @@ function embedTag(
241320
// top level embedded nodes have been moved from their normal position in the
242321
// node tree. if there is a comment referring to it, it must be recreated at
243322
// the new position.
244-
return concat([...comments, result, hardline]);
323+
return [...comments, result, hardline];
245324
} else {
246-
return comments.length ? concat([...comments, result]) : result;
325+
return comments.length ? [...comments, result] : result;
326+
}
327+
}
328+
329+
function printJS(
330+
node: any,
331+
forceSingleQuote: boolean,
332+
forceSingleLine: boolean,
333+
removeParentheses: boolean,
334+
name: string,
335+
) {
336+
if (!node[name]) {
337+
return;
247338
}
339+
node[name].isJS = true;
340+
node[name].forceSingleQuote = forceSingleQuote;
341+
node[name].forceSingleLine = forceSingleLine;
342+
node[name].removeParentheses = removeParentheses;
248343
}

0 commit comments

Comments
 (0)