From 00162422936286f0672df8d9671fe2edfd385109 Mon Sep 17 00:00:00 2001 From: sooniter Date: Mon, 7 Sep 2026 16:19:52 +0800 Subject: [PATCH] feat(fmt): prototype SWC Next 0.2.0 npm integration --- packages/rstack/SWC_NEXT_POC.md | 30 ++ packages/rstack/THIRD_PARTY_NOTICES.md | 4 +- packages/rstack/package.json | 4 +- packages/rstack/src/fmt/cacheIdentity.ts | 1 + packages/rstack/src/fmt/prettierPlugins.ts | 6 +- packages/rstack/src/fmt/swcNextParser.ts | 25 ++ .../fmt/{yukuPlugin.ts => swcNextPlugin.ts} | 57 ++-- .../rstack/tests/fmt/cacheIdentity.test.ts | 1 + ...kuPlugin.test.ts => swcNextPlugin.test.ts} | 122 +++++--- pnpm-lock.yaml | 290 +++++++++--------- pnpm-workspace.yaml | 17 +- website/docs/en/guide/formatting.mdx | 4 +- website/docs/zh/guide/formatting.mdx | 4 +- 13 files changed, 337 insertions(+), 228 deletions(-) create mode 100644 packages/rstack/SWC_NEXT_POC.md create mode 100644 packages/rstack/src/fmt/swcNextParser.ts rename packages/rstack/src/fmt/{yukuPlugin.ts => swcNextPlugin.ts} (92%) rename packages/rstack/tests/fmt/{yukuPlugin.test.ts => swcNextPlugin.test.ts} (71%) diff --git a/packages/rstack/SWC_NEXT_POC.md b/packages/rstack/SWC_NEXT_POC.md new file mode 100644 index 00000000..d46c5cab --- /dev/null +++ b/packages/rstack/SWC_NEXT_POC.md @@ -0,0 +1,30 @@ +# SWC next formatter PoC + +This branch replaces the default Yuku parser in `rs fmt` with SWC Next 0.2.0 while retaining Prettier's ESTree printer and the existing formatting adapter. + +`@swc-next/parser@0.2.0` supplies its own platform-specific native binding and decodes its output internally. The rstack native binding is unchanged. + +Inferred JavaScript, JSX, TypeScript, TSX, and embedded script formatting use SWC Next. Explicit `babel` and `typescript` options still select Prettier's own parsers, and project plugins retain precedence. The experimental explicit parser names are `swc-next` and `swc-next-ts`, replacing `yuku` and `yuku-ts`. + +The cache namespace includes the parser version and integration route so switching between the original formatter and either PoC cannot reuse stale formatting results. The decoder and Rust serializer must be upgraded together; all SWC Next packages in this PoC are locked to 0.2.0. + +## Try it + +```sh +pnpm install +pnpm build +pnpm --filter rstack build:native +printf 'const view=' | pnpm exec rs fmt --stdin-filepath example.tsx +pnpm test +pnpm check +``` + +## Compatibility and limits + +SWC Next 0.2.0 accepts function implementations in declaration files, for example `export function value() { return 1; }` in `example.d.ts`. Yuku previously rejected this with an ambient-context diagnostic. The tests explicitly record this upstream difference; valid declarations, comments, Unicode locations, JSX/TSX, CommonJS, pragmas, and syntax errors remain covered. No fallback to another parser hides SWC Next errors. + +Validation is on macOS arm64 with the repository's Node.js, pnpm, and Rust versions. The complete formatter/CLI suite covers worker execution, stdin, LSP, Vue, Svelte, and caching. Cross-platform binaries and formatter-wide conformance against Prettier's upstream corpus have not been validated. No throughput advantage is claimed by this PoC. + +## Upstream + +[SWC Next 0.2.0 source](https://github.com/swc-project/swc-next/tree/v0.2.0) diff --git a/packages/rstack/THIRD_PARTY_NOTICES.md b/packages/rstack/THIRD_PARTY_NOTICES.md index a4f4fa3e..4948fef4 100644 --- a/packages/rstack/THIRD_PARTY_NOTICES.md +++ b/packages/rstack/THIRD_PARTY_NOTICES.md @@ -29,9 +29,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -## Prettier yuku parser adapter +## Prettier SWC next parser adapter -The local Yuku parser adapter includes portions derived from +The local SWC Next parser adapter includes portions derived from [@prettier/plugin-yuku](https://github.com/prettier/prettier/tree/main/packages/plugin-yuku) and Prettier's JavaScript parser postprocessing. The adapter reuses the public ESTree printer, formatter options, and parser utilities from the installed diff --git a/packages/rstack/package.json b/packages/rstack/package.json index 0f8aed3b..f6529cf6 100644 --- a/packages/rstack/package.json +++ b/packages/rstack/package.json @@ -77,9 +77,9 @@ "@rslib/core": "catalog:", "@rslint/core": "catalog:", "@rstest/core": "catalog:", + "@swc-next/parser": "catalog:", "prettier": "catalog:", - "tinypool": "catalog:", - "yuku-parser": "catalog:" + "tinypool": "catalog:" }, "devDependencies": { "@napi-rs/cli": "catalog:", diff --git a/packages/rstack/src/fmt/cacheIdentity.ts b/packages/rstack/src/fmt/cacheIdentity.ts index 81d7bdd6..033ae9b7 100644 --- a/packages/rstack/src/fmt/cacheIdentity.ts +++ b/packages/rstack/src/fmt/cacheIdentity.ts @@ -21,6 +21,7 @@ const cacheNamespace: string = JSON.stringify([ fmtCacheVersion, RSTACK_VERSION, PRETTIER_VERSION, + 'swc-next@0.2.0:npm', ]); /** Creates project-relative POSIX cache keys without repeating path setup. */ diff --git a/packages/rstack/src/fmt/prettierPlugins.ts b/packages/rstack/src/fmt/prettierPlugins.ts index 5dc633f5..9c3cb2b2 100644 --- a/packages/rstack/src/fmt/prettierPlugins.ts +++ b/packages/rstack/src/fmt/prettierPlugins.ts @@ -1,6 +1,6 @@ import type { Options as PrettierOptions, Plugin } from 'prettier'; import type { ResolvedFmtOptions } from './types.ts'; -import { yukuPlugin } from './yukuPlugin.ts'; +import { swcNextPlugin } from './swcNextPlugin.ts'; type PrettierPlugins = NonNullable; @@ -15,14 +15,14 @@ const fmtOptionsPlugin = { }, } satisfies Plugin; -const defaultFmtPlugins: PrettierPlugins = [yukuPlugin, fmtOptionsPlugin]; +const defaultFmtPlugins: PrettierPlugins = [swcNextPlugin, fmtOptionsPlugin]; /** Prepends bundled plugins so project plugins can override their parsers. */ const getPrettierPlugins = async ( options: ResolvedFmtOptions, filePath: string, ): Promise => { - // An explicit native parser is also the escape hatch for bypassing Yuku. + // An explicit native parser is also the escape hatch for bypassing SWC Next. const defaultPlugins = options.parser === 'babel' || options.parser === 'typescript' ? [fmtOptionsPlugin] diff --git a/packages/rstack/src/fmt/swcNextParser.ts b/packages/rstack/src/fmt/swcNextParser.ts new file mode 100644 index 00000000..6c9633ee --- /dev/null +++ b/packages/rstack/src/fmt/swcNextParser.ts @@ -0,0 +1,25 @@ +import { + langFromPath, + parseSync as parse, + type ParserOptions, + type ParseResult as SwcParseResult, +} from '@swc-next/parser'; + +export { langFromPath }; +export type { Comment, Diagnostic } from '@swc-next/parser'; +export type Lang = 'js' | 'jsx' | 'ts' | 'tsx' | 'dts'; +export type SourceType = 'module' | 'commonjs'; +export type ParseOptions = { + lang: Lang; + sourceType: SourceType; + preserveParens: true; + comments: 'flat'; +}; + +// Keep the upstream untyped AST behind an unknown boundary. +export type ParseResult = Omit & { + program: unknown; +}; + +export const parseSync = (source: string, options: ParseOptions): ParseResult => + parse(source, options as ParserOptions); diff --git a/packages/rstack/src/fmt/yukuPlugin.ts b/packages/rstack/src/fmt/swcNextPlugin.ts similarity index 92% rename from packages/rstack/src/fmt/yukuPlugin.ts rename to packages/rstack/src/fmt/swcNextPlugin.ts index bd6d74f3..932fe086 100644 --- a/packages/rstack/src/fmt/yukuPlugin.ts +++ b/packages/rstack/src/fmt/swcNextPlugin.ts @@ -2,16 +2,16 @@ import * as prettierEstreePlugin from 'prettier/plugins/estree'; import type { Parser, ParserOptions, Plugin } from 'prettier'; import { langFromPath, - parse as parseWithYuku, + parseSync as parseWithSwcNext, type Comment, type Diagnostic, type ParseOptions, type ParseResult, - type SourceLang, + type Lang, type SourceType, -} from 'yuku-parser'; +} from './swcNextParser.ts'; -const AST_FORMAT = 'estree-yuku'; +const AST_FORMAT = 'estree-swc-next'; 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']; @@ -158,7 +158,7 @@ const isAstNode = (value: unknown): value is AstNode => const asAstNode = (value: unknown): AstNode => { if (!isAstNode(value)) { - throw new TypeError('Expected a Yuku AST node.'); + throw new TypeError('Expected a SWC Next AST node.'); } return value; }; @@ -220,7 +220,7 @@ const stripComments = ( const chunks: string[] = []; let cursor = 0; - // Yuku returns comments in source order, so mask each range while copying the source only once. + // SWC Next returns comments in source order, so mask each range while copying the source only once. for (const comment of comments) { const start = locStart(comment); const end = locEnd(comment); @@ -356,7 +356,7 @@ const postprocess = ( ast: AstNode, comments: PrettierComment[], text: string, - astType: 'yuku-js' | 'yuku-ts', + astType: 'swc-next-js' | 'swc-next-ts', ): AstNode => { mergeNestedJsdocComments(comments); @@ -384,7 +384,7 @@ const postprocess = ( const expression = asAstNode(node.expression); const start = locStart(node); - // Yuku comments are in source order, so these end offsets are sorted. + // SWC Next comments are in source order, so these end offsets are sorted. typeCastCommentEnds ??= comments .filter(isTypeCastComment) .map((comment) => locEnd(comment)); @@ -415,7 +415,7 @@ const postprocess = ( } case 'TemplateElement': { - if (astType === 'yuku-ts') { + if (astType === 'swc-next-ts') { const start = locStart(node) + 1; const end = locEnd(node) - (node.tail ? 1 : 2); node.range = [start, end]; @@ -482,11 +482,13 @@ const createParseError = (error: Diagnostic, text: string): SyntaxError => { ); }; -const parseWithOptions = (text: string, options: ParseOptions): ParseResult => { - const result = parseWithYuku(text, { +const parseWithOptions = ( + text: string, + options: Pick, +): ParseResult => { + const result = parseWithSwcNext(text, { preserveParens: true, - semanticErrors: false, - attachComments: false, + comments: 'flat', ...options, }); @@ -509,10 +511,7 @@ const getSourceType = (filepath: string): SourceType | undefined => { return undefined; }; -const getLanguageCombinations = ( - text: string, - filepath: string, -): SourceLang[] => { +const getLanguageCombinations = (text: string, filepath: string): Lang[] => { const normalizedPath = filepath.toLowerCase(); if (JS_TS_FILE_REGEXP.test(normalizedPath)) { @@ -542,7 +541,7 @@ const tryCombinations = (combinations: (() => ParseResult)[]): ParseResult => { throw firstError; } - throw new Error('No Yuku parser combinations were provided.'); + throw new Error('No SWC Next parser combinations were provided.'); }; const parseJavaScript = ( @@ -558,7 +557,7 @@ const parseJavaScript = ( ); const { program, comments } = tryCombinations(combinations); - return postprocess(program as unknown as AstNode, comments, text, 'yuku-js'); + return postprocess(asAstNode(program), comments, text, 'swc-next-js'); }; const parseTypeScript = ( @@ -576,7 +575,7 @@ const parseTypeScript = ( ); const { program, comments } = tryCombinations(combinations); - return postprocess(program as unknown as AstNode, comments, text, 'yuku-ts'); + return postprocess(asAstNode(program), comments, text, 'swc-next-ts'); }; const createParser = ( @@ -611,23 +610,23 @@ const parseBabel = (text: string, options: ParserOptions): AstNode => { }; }; -const yukuParser = createParser(parseJavaScript); -const yukuBabelParser = createParser(parseBabel); -const yukuTypeScriptParser = createParser(parseTypeScript); +const swcNextParser = createParser(parseJavaScript); +const swcNextBabelParser = createParser(parseBabel); +const swcNextTypeScriptParser = createParser(parseTypeScript); -const yukuPlugin: Plugin = { +const swcNextPlugin: Plugin = { options: estreePlugin.options, parsers: { // Prettier resolves parsers from the last plugin that provides the name. // Project plugins are loaded after this one, so parser wrappers take priority. - babel: yukuBabelParser, - typescript: yukuTypeScriptParser, - yuku: yukuParser, - 'yuku-ts': yukuTypeScriptParser, + babel: swcNextBabelParser, + typescript: swcNextTypeScriptParser, + 'swc-next': swcNextParser, + 'swc-next-ts': swcNextTypeScriptParser, }, printers: { [AST_FORMAT]: estreePrinter, }, }; -export { yukuPlugin }; +export { swcNextPlugin }; diff --git a/packages/rstack/tests/fmt/cacheIdentity.test.ts b/packages/rstack/tests/fmt/cacheIdentity.test.ts index 29f2bebb..5ae065b7 100644 --- a/packages/rstack/tests/fmt/cacheIdentity.test.ts +++ b/packages/rstack/tests/fmt/cacheIdentity.test.ts @@ -88,6 +88,7 @@ test('includes formatter implementation versions in the namespace', () => { fmtCacheVersion, pkgJson.version, prettierPkgJson.version, + 'swc-next@0.2.0:npm', ]); }); diff --git a/packages/rstack/tests/fmt/yukuPlugin.test.ts b/packages/rstack/tests/fmt/swcNextPlugin.test.ts similarity index 71% rename from packages/rstack/tests/fmt/yukuPlugin.test.ts rename to packages/rstack/tests/fmt/swcNextPlugin.test.ts index 59e7e395..59cc3d52 100644 --- a/packages/rstack/tests/fmt/yukuPlugin.test.ts +++ b/packages/rstack/tests/fmt/swcNextPlugin.test.ts @@ -6,28 +6,33 @@ import { } from 'prettier'; import { expect, test } from 'rstack/test'; import { getPrettierPlugins } from '../../src/fmt/prettierPlugins.ts'; -import { yukuPlugin } from '../../src/fmt/yukuPlugin.ts'; +import { swcNextPlugin } from '../../src/fmt/swcNextPlugin.ts'; -const formatWithYuku = ( +const formatWithSwcNext = ( source: string, - options: Options & { parser: 'yuku' | 'yuku-ts' }, + options: Options & { parser: 'swc-next' | 'swc-next-ts' }, ): Promise => format(source, { - plugins: [yukuPlugin], + plugins: [swcNextPlugin], ...options, filepath: - options.filepath ?? `example.${options.parser === 'yuku' ? 'js' : 'ts'}`, + options.filepath ?? + `example.${options.parser === 'swc-next' ? 'js' : 'ts'}`, }); test('overrides the default JavaScript and TypeScript parsers', async () => { - expect(yukuPlugin.languages).toBeUndefined(); - expect(yukuPlugin.parsers?.babel).not.toBe(yukuPlugin.parsers?.yuku); - expect(yukuPlugin.parsers?.typescript).toBe(yukuPlugin.parsers?.['yuku-ts']); + expect(swcNextPlugin.languages).toBeUndefined(); + expect(swcNextPlugin.parsers?.babel).not.toBe( + swcNextPlugin.parsers?.['swc-next'], + ); + expect(swcNextPlugin.parsers?.typescript).toBe( + swcNextPlugin.parsers?.['swc-next-ts'], + ); await expect( Promise.all( ['js', 'jsx', 'ts', 'tsx'].map(async (extension) => - getFileInfo(`example.${extension}`, { plugins: [yukuPlugin] }), + getFileInfo(`example.${extension}`, { plugins: [swcNextPlugin] }), ), ), ).resolves.toEqual([ @@ -39,9 +44,9 @@ test('overrides the default JavaScript and TypeScript parsers', async () => { }); test('matches the native Babel AST root shape', async () => { - const parser = yukuPlugin.parsers?.babel; + const parser = swcNextPlugin.parsers?.babel; if (!parser) { - throw new Error('The Babel-compatible Yuku parser is not registered.'); + throw new Error('The Babel-compatible SWC Next parser is not registered.'); } const ast = (await parser.parse('// comment\nconst value = 1', { @@ -63,15 +68,15 @@ test.each(['babel', 'typescript'] as const)( { parser }, `example.${parser === 'babel' ? 'js' : 'ts'}`, ), - ).resolves.not.toContain(yukuPlugin); + ).resolves.not.toContain(swcNextPlugin); }, ); test('parses JSX in JavaScript files', async () => { await expect( - formatWithYuku('const view=', { + formatWithSwcNext('const view=', { filepath: 'example.js', - parser: 'yuku', + parser: 'swc-next', }), ).resolves.toBe('const view = ;\n'); }); @@ -80,74 +85,73 @@ test.each(['example.ts', 'example.mts', 'example.cts'])( 'rejects JSX syntax in %s', async (filepath) => { await expect( - formatWithYuku('const view=', { + formatWithSwcNext('const view=', { filepath, - parser: 'yuku-ts', + parser: 'swc-next-ts', }), ).rejects.toThrow(); }, ); test.each(['example.d.ts', 'example.d.mts', 'example.d.cts'])( - 'rejects function implementations in %s', + 'records the SWC Next 0.2.0 ambient implementation diagnostic gap in %s', async (filepath) => { + // Unlike Yuku, SWC Next 0.2.0 leaves this ambient-context error to a type checker. await expect( - formatWithYuku('export function value() { return 1; }', { + formatWithSwcNext('export function value() { return 1; }', { filepath, - parser: 'yuku-ts', + parser: 'swc-next-ts', }), - ).rejects.toThrow( - 'An implementation cannot be declared in ambient contexts', - ); + ).resolves.toBe('export function value() {\n return 1;\n}\n'); }, ); test.each([ { name: 'hashbangs and unicode locations', - parser: 'yuku' as const, + parser: 'swc-next' as const, source: '#!/usr/bin/env node\n// 中文 😀\nconst 你好={值:"😀"}', expected: '#!/usr/bin/env node\n// 中文 😀\nconst 你好 = { 值: "😀" };\n', }, { name: 'Closure-style type casts', - parser: 'yuku' as const, + parser: 'swc-next' as const, source: '/** @type {Foo} */ (value).method()', expected: '/** @type {Foo} */ (value).method();\n', }, { name: 'comments before semicolons', - parser: 'yuku' as const, + parser: 'swc-next' as const, source: 'foo /* trailing */ ;', expected: 'foo; /* trailing */\n', }, { name: 'adjacent multiline JSDoc comments', - parser: 'yuku' as const, + parser: 'swc-next' as const, source: '/**\n * outer\n *//**\n * inner\n */\nfoo()', expected: '/**\n * outer\n *//**\n * inner\n */\nfoo();\n', }, { name: 'right-nested logical expressions', - parser: 'yuku' as const, + parser: 'swc-next' as const, source: 'const value = a || (b || c)', expected: 'const value = a || b || c;\n', }, { name: 'parenthesized TypeScript types', - parser: 'yuku-ts' as const, + parser: 'swc-next-ts' as const, source: 'type Value = (((string | number)));', expected: 'type Value = string | number;\n', }, { name: 'TypeScript template expressions', - parser: 'yuku-ts' as const, + parser: 'swc-next-ts' as const, source: 'const result = `value: ${foo satisfies string}`', expected: 'const result = `value: ${foo satisfies string}`;\n', }, { name: 'TSX expressions', - parser: 'yuku-ts' as const, + parser: 'swc-next-ts' as const, filepath: 'example.tsx', source: 'const view=({(item)})', expected: @@ -155,7 +159,7 @@ test.each([ }, ])('normalizes $name for the ESTree printer', async (fixture) => { await expect( - formatWithYuku(fixture.source, { + formatWithSwcNext(fixture.source, { filepath: fixture.filepath, parser: fixture.parser, }), @@ -164,17 +168,17 @@ test.each([ test('reuses Prettier options and pragma handling', async () => { await expect( - formatWithYuku('/** @format */\nconst value={answer:"yes"}', { - parser: 'yuku', + formatWithSwcNext('/** @format */\nconst value={answer:"yes"}', { + parser: 'swc-next', requirePragma: true, singleQuote: true, }), ).resolves.toBe("/** @format */\nconst value = { answer: 'yes' };\n"); await expect( - formatWithYuku('/** @noformat */\nconst value={answer:"yes"}', { + formatWithSwcNext('/** @noformat */\nconst value={answer:"yes"}', { checkIgnorePragma: true, - parser: 'yuku', + parser: 'swc-next', }), ).resolves.toBe('/** @noformat */\nconst value={answer:"yes"}'); }); @@ -218,9 +222,9 @@ test.each([ ])( 'matches Prettier pragma detection for $source', ({ source, hasPragma, hasIgnorePragma }) => { - const parser = yukuPlugin.parsers?.yuku; + const parser = swcNextPlugin.parsers?.['swc-next']; if (!parser?.hasPragma || !parser.hasIgnorePragma) { - throw new Error('The Yuku parser does not expose pragma handlers.'); + throw new Error('The SWC Next parser does not expose pragma handlers.'); } expect(parser.hasPragma(source)).toBe(hasPragma); @@ -229,9 +233,9 @@ test.each([ ); test('matches Prettier JavaScript location overrides', () => { - const parser = yukuPlugin.parsers?.yuku; + const parser = swcNextPlugin.parsers?.['swc-next']; if (!parser) { - throw new Error('The Yuku parser is not registered.'); + throw new Error('The SWC Next parser is not registered.'); } expect( @@ -303,17 +307,17 @@ test('matches Prettier JavaScript location overrides', () => { test('supports CommonJS source semantics for .cjs files', async () => { await expect( - formatWithYuku('return require("example")', { + formatWithSwcNext('return require("example")', { filepath: 'example.cjs', - parser: 'yuku', + parser: 'swc-next', }), ).resolves.toBe('return require("example");\n'); }); test('matches the official hashbang AST shape', async () => { - const parser = yukuPlugin.parsers?.yuku; + const parser = swcNextPlugin.parsers?.['swc-next']; if (!parser) { - throw new Error('The Yuku parser is not registered.'); + throw new Error('The SWC Next parser is not registered.'); } const options = { filepath: 'example.js' } as ParserOptions; @@ -331,9 +335,9 @@ test('matches the official hashbang AST shape', async () => { expect(Object.hasOwn(astWithHashbang, 'hashbang')).toBe(false); }); -test('reports Yuku diagnostics with Prettier locations', async () => { - const error = await formatWithYuku('\n\nconst = 1', { - parser: 'yuku-ts', +test('reports SWC Next diagnostics with Prettier locations', async () => { + const error = await formatWithSwcNext('\n\nconst = 1', { + parser: 'swc-next-ts', }).catch((error: unknown) => error); expect(error).toBeInstanceOf(SyntaxError); @@ -349,3 +353,29 @@ test('reports Yuku diagnostics with Prettier locations', async () => { end: { column: 8, line: 3 }, }); }); + +test.each(['js', 'jsx', 'ts', 'tsx'])( + 'uses SWC Next by default for .%s files', + async (extension) => { + const plugins = await getPrettierPlugins({}, `example.${extension}`); + expect(plugins).toContain(swcNextPlugin); + await expect( + format('const value = {answer:42}', { + filepath: `example.${extension}`, + plugins, + }), + ).resolves.toBe('const value = { answer: 42 };\n'); + }, +); + +test.each(['example.d.ts', 'example.d.mts', 'example.d.cts'])( + 'formats declarations in %s', + async (filepath) => { + await expect( + formatWithSwcNext('export declare function value(x:string):number', { + filepath, + parser: 'swc-next-ts', + }), + ).resolves.toBe('export declare function value(x: string): number;\n'); + }, +); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 41a43880..ee1a4f25 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -58,6 +58,9 @@ catalogs: '@shikijs/transformers': specifier: ^4.4.3 version: 4.4.3 + '@swc-next/parser': + specifier: 0.2.0 + version: 0.2.0 '@testing-library/dom': specifier: ^10.4.1 version: 10.4.1 @@ -148,9 +151,6 @@ catalogs: vscode-languageserver-textdocument: specifier: 1.0.14 version: 1.0.14 - yuku-parser: - specifier: 0.9.3 - version: 0.9.3 importers: @@ -373,15 +373,15 @@ importers: '@rstest/core': specifier: 'catalog:' version: 0.11.12(happy-dom@20.13.2) + '@swc-next/parser': + specifier: 'catalog:' + version: 0.2.0 prettier: specifier: 'catalog:' version: 3.9.6 tinypool: specifier: 'catalog:' version: 2.1.2 - yuku-parser: - specifier: 'catalog:' - version: 0.9.3 devDependencies: '@napi-rs/cli': specifier: 'catalog:' @@ -1602,6 +1602,90 @@ packages: peerDependencies: acorn: ^8.9.0 + '@swc-next/decoder@0.2.0': + resolution: {integrity: sha512-1VG4sU5uzhLTrHPNELAqMY5+ek1RnHfwgCBSlZ0PfXy08yIGQ0hCluSSnXgPoJ3Iq5qIWktzSG6RF4GAGbSFRw==} + + '@swc-next/parser-binding-darwin-arm64@0.2.0': + resolution: {integrity: sha512-YKdcj1hR3d5PWiY4MmB7bUus/yisaA/ZjxYXLLZdP1LT9ZqyIJRc/MfonkQWvu7VieEwnH1WEeCEPk7gi9+Qjg==} + cpu: [arm64] + os: [darwin] + + '@swc-next/parser-binding-darwin-x64@0.2.0': + resolution: {integrity: sha512-WVrEzvMwvRsOYELckyZD1dHLYxIJ7IrwsN7+NHLaB/3zF1FxnlyL3beeQr/3YB/O3FBJ/4cG0RFbjhjpXPXK8g==} + cpu: [x64] + os: [darwin] + + '@swc-next/parser-binding-linux-arm64-gnu@0.2.0': + resolution: {integrity: sha512-r3rMyr0wE7obCYBoW+9FHa+ThgR2KCOBGz2tTvPHqTHU6Fn0NVotWgiEnf8vffPzMSVVtui2oZD3IxRgR3+Siw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-arm64-musl@0.2.0': + resolution: {integrity: sha512-nHHbZiuTdCm5bFEYNLQGyfzvfFMJVeplmMQnoieeoJCNiMLTpJ5Gnow2OIhbxiBLYpqpsJLV0zlpY1Yq/54slQ==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc-next/parser-binding-linux-ppc64-gnu@0.2.0': + resolution: {integrity: sha512-8jXHm8vC9RlfZn4ARZau6QOBLvMVknUen+O4pjmo8p6e0u8mtO1goc7Vz0rDIyB/ypJNj5a7eOFUDSmmYsWs5w==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-riscv64-gnu@0.2.0': + resolution: {integrity: sha512-Cc8lDmL7TDSm2qL3we0tI7tK2i8i0vm2h0BY0sL8+0mDZUm7vaAqPRa44VxXC30QeO9qk39+YY3W8Rn1ONEIWQ==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-riscv64-musl@0.2.0': + resolution: {integrity: sha512-qtYKgefTHn9I5QWGebCOVGo1I2o56tM3rWYpFZrqRPRJbTRRsYhOfOir+0w/nhnVAu0tTV1pSw9BIr73DoOfKg==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@swc-next/parser-binding-linux-s390x-gnu@0.2.0': + resolution: {integrity: sha512-StfgizIxk6eWC6gMG2zt6gRc7uLur8coARiS12vky/AgRZhMfaBHsm0XQXegcZx6JWJtLc32e5MDn1jUuPimKA==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-x64-gnu@0.2.0': + resolution: {integrity: sha512-RtQE0Yuqrnq0fF9pW/5JRnVOQaSzEnptWGDHGJDBW8aE7ezeN7GwsG4yNKSqemVAPK8Gnl5w6+3JQOttSWrT0Q==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-x64-musl@0.2.0': + resolution: {integrity: sha512-mhF7DPtpBw5ZLslrWJRMjJ+LTIlcuDiBP6kGTAkWINvjbZ0IexyAl5RPu1QCYX9P8e5btsLGGuKzQLSM9J8D7w==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc-next/parser-binding-win32-arm64-msvc@0.2.0': + resolution: {integrity: sha512-s3V7C9mNalM96tADVkgSFBa9G5Oz9Yx03j3wS3DojRyya1OZx44ccFNz5Py69hodSdP7BAWze2ly7ruxyjA/DQ==} + cpu: [arm64] + os: [win32] + + '@swc-next/parser-binding-win32-ia32-msvc@0.2.0': + resolution: {integrity: sha512-SBIE155ckk+L9CFDzFu14Rsrj4dhJ9d4NUsrll/yxIg1RtY4p32zWWKMK9TKY1ZIde4XDRNsqudLfD21g8ZcEw==} + cpu: [ia32] + os: [win32] + + '@swc-next/parser-binding-win32-x64-msvc@0.2.0': + resolution: {integrity: sha512-/qFhGVEXAMd03lxJA1d6v+ozjOxN+DqFXrVJoNU6LwBPFTmL56INi86P+zaHX5CJ6x0rzvyhAGfuDSkh58/f1w==} + cpu: [x64] + os: [win32] + + '@swc-next/parser@0.2.0': + resolution: {integrity: sha512-4K6fIQ/Y/bDA2quYkOb3YKSWlTvSg2ty+Ot5qu2jsg4QU4TK+PqTbTqvMi9CoEIyGolEOGZe1t1hSpDNzYn5fg==} + peerDependencies: + '@swc-next/parser-binding-wasm': 0.2.0 + peerDependenciesMeta: + '@swc-next/parser-binding-wasm': + optional: true + '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -1827,75 +1911,6 @@ packages: peerDependencies: react: '>=18.3.1' - '@yuku-parser/binding-android-arm64@0.9.3': - resolution: {integrity: sha512-z3tDGTaUXD5Q4IuebFOY/QHxhR/SqujMhkMv9C5bh25upyFEXdafp0MsXQIIheWhVZzn5VMOniyxFni/7s9LgQ==} - cpu: [arm64] - os: [android] - - '@yuku-parser/binding-darwin-arm64@0.9.3': - resolution: {integrity: sha512-hzKvKSKS7z3ufnu1VkQYEoxmS6A5uNnkwukKnc2atxpWdq648nLVOqut4h1jUXjbM2WcoTfuZLF6AHAGFf8cmg==} - cpu: [arm64] - os: [darwin] - - '@yuku-parser/binding-darwin-x64@0.9.3': - resolution: {integrity: sha512-OM2PiVlPATvzlj/KGHNlu+t8FC3YzM5joVNjhsz/EaigQ8x2UUQ1Q0agQLiv5GZ2L8TSzrLUwBCZ7YOvP8q+tw==} - cpu: [x64] - os: [darwin] - - '@yuku-parser/binding-freebsd-x64@0.9.3': - resolution: {integrity: sha512-WMn9M4LHNVysGNwsAJ9gpRPqQIW2lcPrDNGcyk0agx3IYqCJq1MQugh6Be8Otc5U08eGdE39o8Kx9YWJmXz7GA==} - cpu: [x64] - os: [freebsd] - - '@yuku-parser/binding-linux-arm-gnu@0.9.3': - resolution: {integrity: sha512-vqKjwiyW1FWvbykYMEQIcAJwA17WxK3rxxqDuyCvQwcNVaLEUxI4BXiUdlF3kHucc7MnoFQhoRPkySgOzlLM+Q==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm-musl@0.9.3': - resolution: {integrity: sha512-qohilhYOT+zkt2gYzym4F1T6BzRdvPqS9/sFB03pmnVV8LrvjOXVsGwEBAa3CEvzJKhAZjdTmVz7zsVdjyHWLg==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-arm64-gnu@0.9.3': - resolution: {integrity: sha512-tvTIyUvTGkeee68i4JIo9o27As+Ug6LXOZvElGgFbTs6+KBdb5LGhvugaIQljdfIsm2XnIbOLG6OnQSXHMLB9w==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm64-musl@0.9.3': - resolution: {integrity: sha512-OWZBHW1wuChBpFlrF+On1yiBcFPMRrj2g0VKsM/PCBGffu1OM0ORkS+vLS3Snu6wYwndM5Dd9Ctvux6eUlrQjA==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-x64-gnu@0.9.3': - resolution: {integrity: sha512-/tbk1h0dlADOCngbiQO9V3SHwBIJkoGBq8BDEraSw2CC3nGxPEPTCCYUDp5738Ij2FxVwy1FWVuhFkHvpMrPbQ==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-x64-musl@0.9.3': - resolution: {integrity: sha512-u3+0sCso/mcjDvtc3866D2giV7l34PDyDVBkMesAWa3DWbIWPlybehi2SSnmScgArV22ctqSSgUzdBBVJ6zYAg==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-win32-arm64@0.9.3': - resolution: {integrity: sha512-xnGEvdhyjRkXozHtpXVpEEkyGZWAxJ3RoILv7XRV5SvTEztaTCk5GQyfcOzI9An/EJtcL4ERCI8QmS0TpDPJiA==} - cpu: [arm64] - os: [win32] - - '@yuku-parser/binding-win32-x64@0.9.3': - resolution: {integrity: sha512-N5eShGcuwnrXEprePFmEjtng6acZmtnC4zgazK9xxkavcx1q1uX8Nz8lU5RS973EMlNr902cIc6Cd2D4tqZDBg==} - cpu: [x64] - os: [win32] - - '@yuku-toolchain/types@0.9.3': - resolution: {integrity: sha512-rFE+5P4g2wxko5C85MugJOlVjBHEQq87dIkhLkniLXLp63PEtgaFjD954i5HXlfnyzLxPcZHsSOVDVgmo1HToA==} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3154,12 +3169,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yuku-ast@0.9.3: - resolution: {integrity: sha512-Kt0PXlPCXdKF1fWFTl7N3DaxsVk6DHF8VAXHJMkwPtJnWYgbx20pYgGSweuC/86mIM7k1NUITQ4JffgOLUWTCw==} - - yuku-parser@0.9.3: - resolution: {integrity: sha512-96wPoHnwaXfkZv7UIOUkDb+s7ZH8lv8Qtg+MxdvHUV1LFa5jV9iKOaams1942YQt+krFQrWiD6lSg2ermv5kHA==} - zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} @@ -4170,6 +4179,65 @@ snapshots: dependencies: acorn: 8.17.0 + '@swc-next/decoder@0.2.0': {} + + '@swc-next/parser-binding-darwin-arm64@0.2.0': + optional: true + + '@swc-next/parser-binding-darwin-x64@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-arm64-gnu@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-arm64-musl@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-ppc64-gnu@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-riscv64-gnu@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-riscv64-musl@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-s390x-gnu@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-x64-gnu@0.2.0': + optional: true + + '@swc-next/parser-binding-linux-x64-musl@0.2.0': + optional: true + + '@swc-next/parser-binding-win32-arm64-msvc@0.2.0': + optional: true + + '@swc-next/parser-binding-win32-ia32-msvc@0.2.0': + optional: true + + '@swc-next/parser-binding-win32-x64-msvc@0.2.0': + optional: true + + '@swc-next/parser@0.2.0': + dependencies: + '@swc-next/decoder': 0.2.0 + optionalDependencies: + '@swc-next/parser-binding-darwin-arm64': 0.2.0 + '@swc-next/parser-binding-darwin-x64': 0.2.0 + '@swc-next/parser-binding-linux-arm64-gnu': 0.2.0 + '@swc-next/parser-binding-linux-arm64-musl': 0.2.0 + '@swc-next/parser-binding-linux-ppc64-gnu': 0.2.0 + '@swc-next/parser-binding-linux-riscv64-gnu': 0.2.0 + '@swc-next/parser-binding-linux-riscv64-musl': 0.2.0 + '@swc-next/parser-binding-linux-s390x-gnu': 0.2.0 + '@swc-next/parser-binding-linux-x64-gnu': 0.2.0 + '@swc-next/parser-binding-linux-x64-musl': 0.2.0 + '@swc-next/parser-binding-win32-arm64-msvc': 0.2.0 + '@swc-next/parser-binding-win32-ia32-msvc': 0.2.0 + '@swc-next/parser-binding-win32-x64-msvc': 0.2.0 + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -4340,44 +4408,6 @@ snapshots: react: 19.2.8 unhead: 2.1.17 - '@yuku-parser/binding-android-arm64@0.9.3': - optional: true - - '@yuku-parser/binding-darwin-arm64@0.9.3': - optional: true - - '@yuku-parser/binding-darwin-x64@0.9.3': - optional: true - - '@yuku-parser/binding-freebsd-x64@0.9.3': - optional: true - - '@yuku-parser/binding-linux-arm-gnu@0.9.3': - optional: true - - '@yuku-parser/binding-linux-arm-musl@0.9.3': - optional: true - - '@yuku-parser/binding-linux-arm64-gnu@0.9.3': - optional: true - - '@yuku-parser/binding-linux-arm64-musl@0.9.3': - optional: true - - '@yuku-parser/binding-linux-x64-gnu@0.9.3': - optional: true - - '@yuku-parser/binding-linux-x64-musl@0.9.3': - optional: true - - '@yuku-parser/binding-win32-arm64@0.9.3': - optional: true - - '@yuku-parser/binding-win32-x64@0.9.3': - optional: true - - '@yuku-toolchain/types@0.9.3': {} - acorn-jsx@5.3.2(acorn@8.17.0): dependencies: acorn: 8.17.0 @@ -5907,28 +5937,6 @@ snapshots: yaml@2.9.0: optional: true - yuku-ast@0.9.3: - dependencies: - '@yuku-toolchain/types': 0.9.3 - - yuku-parser@0.9.3: - dependencies: - '@yuku-toolchain/types': 0.9.3 - yuku-ast: 0.9.3 - optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.9.3 - '@yuku-parser/binding-darwin-arm64': 0.9.3 - '@yuku-parser/binding-darwin-x64': 0.9.3 - '@yuku-parser/binding-freebsd-x64': 0.9.3 - '@yuku-parser/binding-linux-arm-gnu': 0.9.3 - '@yuku-parser/binding-linux-arm-musl': 0.9.3 - '@yuku-parser/binding-linux-arm64-gnu': 0.9.3 - '@yuku-parser/binding-linux-arm64-musl': 0.9.3 - '@yuku-parser/binding-linux-x64-gnu': 0.9.3 - '@yuku-parser/binding-linux-x64-musl': 0.9.3 - '@yuku-parser/binding-win32-arm64': 0.9.3 - '@yuku-parser/binding-win32-x64': 0.9.3 - zimmerframe@1.1.4: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index b8d1160e..3a6bfb12 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -59,7 +59,7 @@ catalog: 'typescript': '^7.0.2' 'vscode-languageserver': '10.1.1' 'vscode-languageserver-textdocument': '1.0.14' - yuku-parser: '0.9.3' + '@swc-next/parser': '0.2.0' dedupePeers: true @@ -72,6 +72,21 @@ hoistPattern: [] # Reduce the risk of installing compromised packages minimumReleaseAge: 1440 minimumReleaseAgeExclude: + - '@swc-next/parser@0.2.0' + - '@swc-next/decoder@0.2.0' + - '@swc-next/parser-binding-darwin-x64@0.2.0' + - '@swc-next/parser-binding-darwin-arm64@0.2.0' + - '@swc-next/parser-binding-linux-x64-gnu@0.2.0' + - '@swc-next/parser-binding-linux-x64-musl@0.2.0' + - '@swc-next/parser-binding-win32-x64-msvc@0.2.0' + - '@swc-next/parser-binding-linux-arm64-gnu@0.2.0' + - '@swc-next/parser-binding-linux-ppc64-gnu@0.2.0' + - '@swc-next/parser-binding-linux-s390x-gnu@0.2.0' + - '@swc-next/parser-binding-win32-ia32-msvc@0.2.0' + - '@swc-next/parser-binding-linux-arm64-musl@0.2.0' + - '@swc-next/parser-binding-win32-arm64-msvc@0.2.0' + - '@swc-next/parser-binding-linux-riscv64-gnu@0.2.0' + - '@swc-next/parser-binding-linux-riscv64-musl@0.2.0' - '@yuku-parser/binding-android-arm64@0.8.3' - '@yuku-parser/binding-darwin-arm64@0.8.3' - '@yuku-parser/binding-darwin-x64@0.8.3' diff --git a/website/docs/en/guide/formatting.mdx b/website/docs/en/guide/formatting.mdx index 123048f9..49e9f960 100644 --- a/website/docs/en/guide/formatting.mdx +++ b/website/docs/en/guide/formatting.mdx @@ -6,10 +6,10 @@ description: 'Format files with Rstack CLI using Prettier-compatible options, pl import { PackageManagerTabs } from '@rspress/core/theme'; -Rstack CLI includes a formatter built on [Prettier](https://prettier.io/). Compared with running Prettier directly, `rs fmt` offers better performance in two ways: +Rstack CLI includes a formatter built on [Prettier](https://prettier.io/). Compared with running Prettier directly, `rs fmt` includes the following: - **Parallel formatting**: Files are formatted concurrently in a worker pool. -- **Yuku parser**: The high-performance [Yuku](https://yuku.fyi/) parser is used by default for JavaScript, JSX, and TypeScript files. +- **SWC Next parser**: The [SWC Next](https://github.com/swc-project/swc-next) parser is used by default for JavaScript, JSX, and TypeScript files. - **Persistent cache**: Content-based results let later runs skip formatting unchanged files. `rs fmt` supports Prettier options and plugins and adds built-in capabilities such as [sorting package.json fields](#sort-package-json). diff --git a/website/docs/zh/guide/formatting.mdx b/website/docs/zh/guide/formatting.mdx index d1067e67..9539354a 100644 --- a/website/docs/zh/guide/formatting.mdx +++ b/website/docs/zh/guide/formatting.mdx @@ -6,10 +6,10 @@ description: '使用 Rstack CLI 格式化文件,支持与 Prettier 兼容的 import { PackageManagerTabs } from '@rspress/core/theme'; -Rstack CLI 提供了基于 [Prettier](https://prettier.io/) 的格式化工具。相比直接使用 Prettier,`rs fmt` 的性能更好,主要得益于以下两点: +Rstack CLI 提供了基于 [Prettier](https://prettier.io/) 的格式化工具。相比直接使用 Prettier,`rs fmt` 提供了以下能力: - **并行格式化**:通过 worker 池并行格式化文件。 -- **Yuku 解析器**:默认使用高性能的 [Yuku](https://yuku.fyi/) 解析器处理 JavaScript、JSX 和 TypeScript 文件。 +- **SWC Next 解析器**:默认使用 [SWC Next](https://github.com/swc-project/swc-next) 解析器处理 JavaScript、JSX 和 TypeScript 文件。 - **持久化缓存**:基于文件内容缓存结果,后续运行可以跳过未变化文件的格式化。 `rs fmt` 兼容 Prettier 的选项和插件,并提供更多内置能力,例如支持[排序 package.json 字段](#sort-package-json)。