Skip to content

Commit 6a1e4d5

Browse files
committed
fix: format TS in the template
If Svelte 5 + lang="ts" present, use the babel-ts parser for template expressions fixes #447
1 parent c10a570 commit 6a1e4d5

File tree

5 files changed

+48
-7
lines changed

5 files changed

+48
-7
lines changed

src/embed.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -161,7 +161,9 @@ export function embed(path: FastPath, _options: Options) {
161161
const embeddedOptions = {
162162
// Prettier only allows string references as parsers from v3 onwards,
163163
// so we need to have another public parser and defer to that
164-
parser: 'svelteExpressionParser',
164+
parser: options._svelte_ts
165+
? 'svelteTSExpressionParser'
166+
: 'svelteExpressionParser',
165167
singleQuote: node.forceSingleQuote ? true : options.singleQuote,
166168
_svelte_asFunction: node.asFunction,
167169
};

src/index.ts

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import { hasPragma, print } from './print';
44
import { ASTNode } from './print/nodes';
55
import { embed, getVisitorKeys } from './embed';
66
import { snipScriptAndStyleTagContent } from './lib/snipTagContent';
7-
import { parse } from 'svelte/compiler';
7+
import { parse, VERSION } from 'svelte/compiler';
88

99
const babelParser = prettierPluginBabel.parsers.babel;
10+
const typescriptParser = prettierPluginBabel.parsers['babel-ts']; // TODO use TypeScript parser in next major?
11+
const isSvelte5Plus = Number(VERSION.split('.')[0]) >= 5;
1012

1113
function locStart(node: any) {
1214
return node.start;
@@ -45,14 +47,16 @@ export const parsers: Record<string, Parser> = {
4547
}
4648
},
4749
preprocess: (text, options) => {
48-
text = snipScriptAndStyleTagContent(text);
49-
text = text.trim();
50+
const result = snipScriptAndStyleTagContent(text);
51+
text = result.text.trim();
5052
// Prettier sets the preprocessed text as the originalText in case
5153
// the Svelte formatter is called directly. In case it's called
5254
// as an embedded parser (for example when there's a Svelte code block
5355
// inside markdown), the originalText is not updated after preprocessing.
5456
// Therefore we do it ourselves here.
5557
options.originalText = text;
58+
// Only Svelte 5 can have TS in the template
59+
(options as any)._svelte_ts = isSvelte5Plus && result.isTypescript;
5660
return text;
5761
},
5862
locStart,
@@ -69,6 +73,19 @@ export const parsers: Record<string, Parser> = {
6973
program = program.expression;
7074
}
7175

76+
return { ...ast, program };
77+
},
78+
},
79+
svelteTSExpressionParser: {
80+
...typescriptParser,
81+
parse: (text: string, options: any) => {
82+
const ast = typescriptParser.parse(text, options);
83+
84+
let program = ast.program.body[0];
85+
if (!options._svelte_asFunction) {
86+
program = program.expression;
87+
}
88+
7289
return { ...ast, program };
7390
},
7491
},

src/lib/snipTagContent.ts

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,23 @@ const scriptRegex =
66
/<!--[^]*?-->|<script((?:\s+[^=>'"\/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/\s]+)*\s*)>([^]*?)<\/script>/g;
77
const styleRegex =
88
/<!--[^]*?-->|<style((?:\s+[^=>'"\/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/\s]+)*\s*)>([^]*?)<\/style>/g;
9+
const langTsRegex = /\slang=["']?ts["']?/;
910

10-
export function snipScriptAndStyleTagContent(source: string): string {
11+
export function snipScriptAndStyleTagContent(source: string): {
12+
text: string;
13+
isTypescript: boolean;
14+
} {
1115
let scriptMatchSpans = getMatchIndexes('script');
1216
let styleMatchSpans = getMatchIndexes('style');
17+
let isTypescript = false;
1318

14-
return snipTagContent(
19+
const text = snipTagContent(
1520
snipTagContent(source, 'script', '{}', styleMatchSpans),
1621
'style',
1722
'',
1823
scriptMatchSpans,
1924
);
25+
return { text, isTypescript };
2026

2127
function getMatchIndexes(tagName: string) {
2228
const regex = getRegexp(tagName);
@@ -44,6 +50,11 @@ export function snipScriptAndStyleTagContent(source: string): string {
4450
if (match.startsWith('<!--') || withinOtherSpan(index)) {
4551
return match;
4652
}
53+
54+
if (langTsRegex.test(attributes)) {
55+
isTypescript = true;
56+
}
57+
4758
const encodedContent = stringToBase64(content);
4859
const newContent = `<${tagName}${attributes} ${snippedTagContentAttribute}="${encodedContent}">${placeholder}</${tagName}>`;
4960

src/options.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import { ParserOptions as PrettierParserOptions, SupportOption } from 'prettier';
22
import { SortOrder, PluginConfig } from '..';
33

4-
export interface ParserOptions<T = any> extends PrettierParserOptions<T>, Partial<PluginConfig> {}
4+
export interface ParserOptions<T = any> extends PrettierParserOptions<T>, Partial<PluginConfig> {
5+
_svelte_ts?: boolean;
6+
}
57

68
function makeChoice(choice: string) {
79
return { value: choice, description: choice };
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
<script lang="ts"></script>
2+
3+
<button
4+
onclick={(e: Event) => {
5+
e as any;
6+
}}>foo</button
7+
>
8+
9+
<button onclick={(e: Event) => e.detail}>{bar as any}</button>

0 commit comments

Comments
 (0)