Skip to content

Commit

Permalink
fix: format TS in the template
Browse files Browse the repository at this point in the history
If Svelte 5 + lang="ts" present, use the babel-ts parser for template expressions

fixes #447
  • Loading branch information
dummdidumm committed Jun 20, 2024
1 parent c10a570 commit 6a1e4d5
Show file tree
Hide file tree
Showing 5 changed files with 48 additions and 7 deletions.
4 changes: 3 additions & 1 deletion src/embed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -161,7 +161,9 @@ export function embed(path: FastPath, _options: Options) {
const embeddedOptions = {
// Prettier only allows string references as parsers from v3 onwards,
// so we need to have another public parser and defer to that
parser: 'svelteExpressionParser',
parser: options._svelte_ts
? 'svelteTSExpressionParser'
: 'svelteExpressionParser',
singleQuote: node.forceSingleQuote ? true : options.singleQuote,
_svelte_asFunction: node.asFunction,
};
Expand Down
23 changes: 20 additions & 3 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import { hasPragma, print } from './print';
import { ASTNode } from './print/nodes';
import { embed, getVisitorKeys } from './embed';
import { snipScriptAndStyleTagContent } from './lib/snipTagContent';
import { parse } from 'svelte/compiler';
import { parse, VERSION } from 'svelte/compiler';

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

function locStart(node: any) {
return node.start;
Expand Down Expand Up @@ -45,14 +47,16 @@ export const parsers: Record<string, Parser> = {
}
},
preprocess: (text, options) => {
text = snipScriptAndStyleTagContent(text);
text = text.trim();
const result = snipScriptAndStyleTagContent(text);
text = result.text.trim();
// Prettier sets the preprocessed text as the originalText in case
// the Svelte formatter is called directly. In case it's called
// as an embedded parser (for example when there's a Svelte code block
// inside markdown), the originalText is not updated after preprocessing.
// Therefore we do it ourselves here.
options.originalText = text;
// Only Svelte 5 can have TS in the template
(options as any)._svelte_ts = isSvelte5Plus && result.isTypescript;
return text;
},
locStart,
Expand All @@ -69,6 +73,19 @@ export const parsers: Record<string, Parser> = {
program = program.expression;
}

return { ...ast, program };
},
},
svelteTSExpressionParser: {
...typescriptParser,
parse: (text: string, options: any) => {
const ast = typescriptParser.parse(text, options);

let program = ast.program.body[0];
if (!options._svelte_asFunction) {
program = program.expression;
}

return { ...ast, program };
},
},
Expand Down
15 changes: 13 additions & 2 deletions src/lib/snipTagContent.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,23 @@ const scriptRegex =
/<!--[^]*?-->|<script((?:\s+[^=>'"\/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/\s]+)*\s*)>([^]*?)<\/script>/g;
const styleRegex =
/<!--[^]*?-->|<style((?:\s+[^=>'"\/\s]+=(?:"[^"]*"|'[^']*'|[^>\s]+)|\s+[^=>'"\/\s]+)*\s*)>([^]*?)<\/style>/g;
const langTsRegex = /\slang=["']?ts["']?/;

export function snipScriptAndStyleTagContent(source: string): string {
export function snipScriptAndStyleTagContent(source: string): {
text: string;
isTypescript: boolean;
} {
let scriptMatchSpans = getMatchIndexes('script');
let styleMatchSpans = getMatchIndexes('style');
let isTypescript = false;

return snipTagContent(
const text = snipTagContent(
snipTagContent(source, 'script', '{}', styleMatchSpans),
'style',
'',
scriptMatchSpans,
);
return { text, isTypescript };

function getMatchIndexes(tagName: string) {
const regex = getRegexp(tagName);
Expand Down Expand Up @@ -44,6 +50,11 @@ export function snipScriptAndStyleTagContent(source: string): string {
if (match.startsWith('<!--') || withinOtherSpan(index)) {
return match;
}

if (langTsRegex.test(attributes)) {
isTypescript = true;
}

const encodedContent = stringToBase64(content);
const newContent = `<${tagName}${attributes} ${snippedTagContentAttribute}="${encodedContent}">${placeholder}</${tagName}>`;

Expand Down
4 changes: 3 additions & 1 deletion src/options.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
import { ParserOptions as PrettierParserOptions, SupportOption } from 'prettier';
import { SortOrder, PluginConfig } from '..';

export interface ParserOptions<T = any> extends PrettierParserOptions<T>, Partial<PluginConfig> {}
export interface ParserOptions<T = any> extends PrettierParserOptions<T>, Partial<PluginConfig> {
_svelte_ts?: boolean;
}

function makeChoice(choice: string) {
return { value: choice, description: choice };
Expand Down
9 changes: 9 additions & 0 deletions test/printer/samples/typescript-template.html.skip
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
<script lang="ts"></script>

<button
onclick={(e: Event) => {
e as any;
}}>foo</button
>

<button onclick={(e: Event) => e.detail}>{bar as any}</button>

0 comments on commit 6a1e4d5

Please sign in to comment.