Skip to content

Commit c94c0d7

Browse files
committed
Add svelte parser, via prettier-plugin-svelte
1 parent 4ee670f commit c94c0d7

File tree

5 files changed

+197
-0
lines changed

5 files changed

+197
-0
lines changed

package-lock.json

Lines changed: 79 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,8 +23,10 @@
2323
"import-fresh": "^3.3.0",
2424
"import-from": "^4.0.0",
2525
"jest": "^27.4.7",
26+
"line-column": "^1.0.2",
2627
"object-hash": "^2.2.0",
2728
"prettier": "^2.5.1",
29+
"prettier-plugin-svelte": "^2.6.0",
2830
"recast": "^0.20.5",
2931
"rimraf": "^3.0.2",
3032
"tailwindcss": "^3.0.15"

prettier.config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,5 @@ module.exports = {
22
semi: false,
33
singleQuote: true,
44
plugins: ['.'],
5+
pluginSearchDirs: ['./tests'], // disable plugin autoload
56
}

src/index.js

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import * as fs from 'fs'
1616
import requireFrom from 'import-from'
1717
import requireFresh from 'import-fresh'
1818
import objectHash from 'object-hash'
19+
import * as svelte from 'prettier-plugin-svelte'
20+
import lineColumn from 'line-column'
1921

2022
let contextMap = new Map()
2123

@@ -290,13 +292,48 @@ function transformCss(ast, { env }) {
290292
}
291293

292294
export const options = {
295+
...svelte.options,
293296
tailwindConfig: {
294297
type: 'string',
295298
category: 'Tailwind CSS',
296299
description: 'TODO',
297300
},
298301
}
299302

303+
export const languages = svelte.languages
304+
export const printers = {
305+
'svelte-ast': {
306+
...svelte.printers['svelte-ast'],
307+
print: (path, options, print) => {
308+
if (!options.__mutatedOriginalText) {
309+
options.__mutatedOriginalText = true
310+
let changes = path.stack[0].changes
311+
if (changes?.length) {
312+
let finder = lineColumn(options.originalText)
313+
314+
for (let change of changes) {
315+
let start = finder.toIndex(
316+
change.loc.start.line,
317+
change.loc.start.column + 1
318+
)
319+
let end = finder.toIndex(
320+
change.loc.end.line,
321+
change.loc.end.column + 1
322+
)
323+
324+
options.originalText =
325+
options.originalText.substring(0, start) +
326+
change.text +
327+
options.originalText.substring(end)
328+
}
329+
}
330+
}
331+
332+
return svelte.printers['svelte-ast'].print(path, options, print)
333+
},
334+
},
335+
}
336+
300337
export const parsers = {
301338
html: createParser(prettierParserHTML.parsers.html, transformHtml(['class'])),
302339
lwc: createParser(prettierParserHTML.parsers.lwc, transformHtml(['class'])),
@@ -333,6 +370,58 @@ export const parsers = {
333370
prettierParserMeriyah.parsers.meriyah,
334371
transformJavaScript
335372
),
373+
svelte: createParser(svelte.parsers.svelte, (ast, { env }) => {
374+
let changes = []
375+
transformSvelte(ast.html, { env, changes })
376+
ast.changes = changes
377+
}),
378+
}
379+
380+
function transformSvelte(ast, { env, changes }) {
381+
for (let attr of ast.attributes ?? []) {
382+
if (attr.name === 'class') {
383+
for (let i = 0; i < attr.value.length; i++) {
384+
let value = attr.value[i]
385+
if (value.type === 'Text') {
386+
let same = value.raw === value.data
387+
value.raw = sortClasses(value.raw, {
388+
env,
389+
ignoreFirst: i > 0 && !/^\s/.test(value.raw),
390+
ignoreLast: i < attr.value.length - 1 && !/\s$/.test(value.raw),
391+
})
392+
value.data = same
393+
? value.raw
394+
: sortClasses(value.data, {
395+
env,
396+
ignoreFirst: i > 0 && !/^\s/.test(value.data),
397+
ignoreLast:
398+
i < attr.value.length - 1 && !/\s$/.test(value.data),
399+
})
400+
} else if (value.type === 'MustacheTag') {
401+
visit(value.expression, {
402+
Literal(node) {
403+
if (isStringLiteral(node)) {
404+
if (sortStringLiteral(node, { env })) {
405+
changes.push({ text: node.raw, loc: node.loc })
406+
}
407+
}
408+
},
409+
TemplateLiteral(node) {
410+
if (sortTemplateLiteral(node, { env })) {
411+
for (let quasi of node.quasis) {
412+
changes.push({ text: quasi.value.raw, loc: quasi.loc })
413+
}
414+
}
415+
},
416+
})
417+
}
418+
}
419+
}
420+
}
421+
422+
for (let child of ast.children ?? []) {
423+
transformSvelte(child, { env, changes })
424+
}
336425
}
337426

338427
// https://lihautan.com/manipulating-ast-with-javascript/

tests/test.js

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const { execSync } = require('child_process')
55
function format(str, options = {}) {
66
return prettier
77
.format(str, {
8+
pluginSearchDirs: [__dirname], // disable plugin autoload
89
plugins: [path.resolve(__dirname, '..')],
910
semi: false,
1011
singleQuote: true,
@@ -134,6 +135,31 @@ let tests = {
134135
'babel-flow': javascript,
135136
espree: javascript,
136137
meriyah: javascript,
138+
svelte: [
139+
t`<div class="${yes}" />`,
140+
t`<div class="${yes} {someVar}" />`,
141+
t`<div class="{someVar} ${yes}" />`,
142+
t`<div class="${yes} {someVar} ${yes}" />`,
143+
t`<div class={'${yes}'} />`,
144+
t`<div class={'${yes}' + '${yes}'} />`,
145+
t`<div class={\`${yes}\`} />`,
146+
t`<div class={\`${yes} \${'${yes}' + \`${yes}\`} ${yes}\`} />`,
147+
t`<div class={\`${no}\${someVar}${no}\`} />`,
148+
t`<div class="${yes} {\`${yes}\`}" />`,
149+
[
150+
`<div class="sm:block uppercase flex{someVar}" />`,
151+
`<div class="uppercase sm:block flex{someVar}" />`,
152+
],
153+
[
154+
`<div class="{someVar}sm:block md:inline flex" />`,
155+
`<div class="{someVar}sm:block flex md:inline" />`,
156+
],
157+
[
158+
`<div class="sm:p-0 p-0 {someVar}sm:block md:inline flex" />`,
159+
`<div class="p-0 sm:p-0 {someVar}sm:block flex md:inline" />`,
160+
],
161+
['<div class={`sm:p-0\np-0`} />', '<div\n class={`p-0\nsm:p-0`}\n/>'],
162+
],
137163
}
138164

139165
describe('parsers', () => {

0 commit comments

Comments
 (0)