Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/rstack/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@
"is-binary-path": "catalog:",
"lint-staged": "catalog:",
"micromatch": "catalog:",
"prettier-plugin-svelte": "catalog:",
"rslog": "catalog:",
"sort-package-json": "catalog:",
"svelte": "catalog:",
"tiny-readdir": "catalog:",
"typescript": "catalog:"
},
Expand Down
20 changes: 17 additions & 3 deletions packages/rstack/src/fmt/yukuPlugin.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,13 @@ import {
type Diagnostic,
type ParseOptions,
type ParseResult,
type SourceLang,
type SourceType,
} from 'yuku-parser';

const AST_FORMAT = 'estree-yuku';
const JS_TS_FILE_REGEXP = /\.(?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$/i;
const JSX_REGEXP = /^[^"'`]*<\/|^[^/]{2}.*\/>/m;
const SOURCE_TYPE_COMBINATIONS: SourceType[] = ['module', 'commonjs'];

type Range = [start: number, end: number];
Expand Down Expand Up @@ -461,6 +464,17 @@ const getSourceType = (filepath: string): SourceType | undefined => {
return undefined;
};

const getLanguageCombinations = (text: string, filepath: string): SourceLang[] => {
const normalizedPath = filepath.toLowerCase();

if (JS_TS_FILE_REGEXP.test(normalizedPath)) {
return [langFromPath(normalizedPath)];
}

// Embedded code from Vue or Svelte keeps the host file path, so detect JSX from its content.
return JSX_REGEXP.test(text) ? ['tsx', 'ts', 'dts'] : ['ts', 'tsx', 'dts'];
};

const tryCombinations = (combinations: (() => ParseResult)[]): ParseResult => {
let firstError: unknown;
let hasError = false;
Expand Down Expand Up @@ -495,9 +509,9 @@ const parseJavaScript = (text: string, options: ParserOptions<AstNode>): AstNode

const parseTypeScript = (text: string, options: ParserOptions<AstNode>): AstNode => {
const sourceType = getSourceType(options.filepath);
const lang = langFromPath(options.filepath.toLowerCase());
const combinations = (sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS).map(
(candidate) => () => parseWithOptions(text, { sourceType: candidate, lang }),
const languages = getLanguageCombinations(text, options.filepath);
const combinations = (sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS).flatMap((candidate) =>
languages.map((lang) => () => parseWithOptions(text, { sourceType: candidate, lang })),
);
const { program, comments } = tryCombinations(combinations);

Expand Down
37 changes: 37 additions & 0 deletions packages/rstack/tests/cli/fmt/svelte.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { expect, test } from 'rstack/test';
import { expectWriteSummary, setupFmtTest } from './helpers.ts';

const { readProjectFile, runFmt, writeProjectFile } = setupFmtTest();

test.each([
{
name: 'instance scripts',
source:
'<script lang="ts">\nlet title=$state<string>("Rstack + Svelte")\n</script>\n\n<h1>{title}</h1>\n',
expected:
'<script lang="ts">\n let title = $state<string>("Rstack + Svelte");\n</script>\n\n<h1>{title}</h1>\n',
},
{
name: 'module scripts',
source:
'<script module lang="ts">\nconst identity=<T>(value:T):T=>value\n</script>\n\n<p>{identity("Rstack")}</p>\n',
expected:
'<script module lang="ts">\n const identity = <T,>(value: T): T => value;\n</script>\n\n<p>{identity("Rstack")}</p>\n',
},
])('formats TypeScript embedded in Svelte $name', ({ source, expected }) => {
writeProjectFile(
'rstack.config.ts',
`import { define } from 'rstack';

define.fmt({ plugins: ['prettier-plugin-svelte'] });
`,
);
writeProjectFile('App.svelte', source);

const result = runFmt(['App.svelte']);

expect(result.status).toBe(0);
expectWriteSummary(result.stdout, 1, 1);
expect(result.stderr).toBe('');
expect(readProjectFile('App.svelte')).toBe(expected);
});
26 changes: 26 additions & 0 deletions packages/rstack/tests/cli/fmt/vue.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { expect, test } from 'rstack/test';
import { expectWriteSummary, setupFmtTest } from './helpers.ts';

const { readProjectFile, runFmt, writeProjectFile } = setupFmtTest();

test.each([
{
name: 'TypeScript',
source: '<script setup lang="ts">\nconst title=ref<string>("Rstack + Vue")\n</script>\n',
expected: '<script setup lang="ts">\nconst title = ref<string>("Rstack + Vue");\n</script>\n',
},
{
name: 'TSX',
source: '<script setup lang="tsx">\nconst view=<Component value={1}/>\n</script>\n',
expected: '<script setup lang="tsx">\nconst view = <Component value={1} />;\n</script>\n',
},
])('formats $name embedded in Vue files', ({ source, expected }) => {
writeProjectFile('App.vue', source);

const result = runFmt(['App.vue']);

expect(result.status).toBe(0);
expectWriteSummary(result.stdout, 1, 1);
expect(result.stderr).toBe('');
expect(readProjectFile('App.vue')).toBe(expected);
});
Loading