diff --git a/.github/renovate.json b/.github/renovate.json index 83900773..e334cc23 100644 --- a/.github/renovate.json +++ b/.github/renovate.json @@ -10,8 +10,8 @@ "@rslib/core", "@rslint/core", "@rstest/core", - "prettier", - "yuku-parser" + "@swc-next/parser", + "prettier" ], "groupName": null, "groupSlug": null, 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 eec8420a..57c4996e 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/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/yukuPlugin.ts b/packages/rstack/src/fmt/swcNextPlugin.ts similarity index 88% rename from packages/rstack/src/fmt/yukuPlugin.ts rename to packages/rstack/src/fmt/swcNextPlugin.ts index bd6d74f3..e8e335db 100644 --- a/packages/rstack/src/fmt/yukuPlugin.ts +++ b/packages/rstack/src/fmt/swcNextPlugin.ts @@ -1,20 +1,26 @@ +// Adapted from @prettier/plugin-yuku to use SWC Next: +// https://github.com/prettier/prettier/tree/main/packages/plugin-yuku + import * as prettierEstreePlugin from 'prettier/plugins/estree'; import type { Parser, ParserOptions, Plugin } from 'prettier'; import { + CommentMode, + Lang, + SourceType, langFromPath, - parse as parseWithYuku, + parseSync as parseWithSwcNext, type Comment, type Diagnostic, - type ParseOptions, type ParseResult, - type SourceLang, - type SourceType, -} from 'yuku-parser'; +} from '@swc-next/parser'; -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']; +const SOURCE_TYPE_COMBINATIONS: SourceType[] = [ + SourceType.Module, + SourceType.CommonJs, +]; type Range = [start: number, end: number]; @@ -158,7 +164,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 +226,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 +362,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 +390,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 +421,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 +488,13 @@ const createParseError = (error: Diagnostic, text: string): SyntaxError => { ); }; -const parseWithOptions = (text: string, options: ParseOptions): ParseResult => { - const result = parseWithYuku(text, { +const parseWithOptions = ( + text: string, + options: { sourceType: SourceType; lang: Lang }, +): ParseResult => { + const result = parseWithSwcNext(text, { preserveParens: true, - semanticErrors: false, - attachComments: false, + comments: CommentMode.Flat, ...options, }); @@ -499,20 +507,17 @@ const parseWithOptions = (text: string, options: ParseOptions): ParseResult => { const getSourceType = (filepath: string): SourceType | undefined => { if (/\.(?:mjs|mts)$/i.test(filepath)) { - return 'module'; + return SourceType.Module; } if (/\.(?:cjs|cts)$/i.test(filepath)) { - return 'commonjs'; + return SourceType.CommonJs; } 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)) { @@ -520,7 +525,9 @@ const getLanguageCombinations = ( } // 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']; + return JSX_REGEXP.test(text) + ? [Lang.Tsx, Lang.Ts, Lang.Dts] + : [Lang.Ts, Lang.Tsx, Lang.Dts]; }; const tryCombinations = (combinations: (() => ParseResult)[]): ParseResult => { @@ -542,7 +549,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 = ( @@ -554,11 +561,16 @@ const parseJavaScript = ( sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS ).map( (candidate) => () => - parseWithOptions(text, { sourceType: candidate, lang: 'jsx' }), + parseWithOptions(text, { sourceType: candidate, lang: Lang.Jsx }), ); - const { program, comments } = tryCombinations(combinations); + const result = tryCombinations(combinations); - return postprocess(program as unknown as AstNode, comments, text, 'yuku-js'); + return postprocess( + asAstNode(result.program), + result.comments, + text, + 'swc-next-js', + ); }; const parseTypeScript = ( @@ -574,9 +586,14 @@ const parseTypeScript = ( (lang) => () => parseWithOptions(text, { sourceType: candidate, lang }), ), ); - const { program, comments } = tryCombinations(combinations); + const result = tryCombinations(combinations); - return postprocess(program as unknown as AstNode, comments, text, 'yuku-ts'); + return postprocess( + asAstNode(result.program), + result.comments, + text, + 'swc-next-ts', + ); }; const createParser = ( @@ -611,23 +628,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/yukuPlugin.test.ts b/packages/rstack/tests/fmt/swcNextPlugin.test.ts similarity index 70% rename from packages/rstack/tests/fmt/yukuPlugin.test.ts rename to packages/rstack/tests/fmt/swcNextPlugin.test.ts index 59e7e395..ae49ed9a 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,9 +85,9 @@ 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(); }, @@ -92,9 +97,9 @@ test.each(['example.d.ts', 'example.d.mts', 'example.d.cts'])( 'rejects function implementations in %s', async (filepath) => { 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', @@ -105,49 +110,49 @@ test.each(['example.d.ts', 'example.d.mts', 'example.d.cts'])( 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 +160,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 +169,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 +223,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 +234,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 +308,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 +336,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 +354,39 @@ 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'); + }, +); + +test('formats typed arrow callbacks in conditional expressions', async () => { + const input = + 'const configs = options ? Object.entries(options).map(([name, content]): ConfigItem => ({ name, content })) : undefined;'; + const options = { filepath: 'example.ts', parser: 'swc-next-ts' as const }; + const formatted = await formatWithSwcNext(input, options); + + expect(formatted).toBe(await format(input, { parser: 'typescript' })); + expect(await formatWithSwcNext(formatted, options)).toBe(formatted); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 79ab9c85..25b59840 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -216,6 +216,9 @@ catalogs: '@shikijs/transformers': specifier: ^4.4.3 version: 4.4.3 + '@swc-next/parser': + specifier: 0.2.2 + version: 0.2.2 '@testing-library/dom': specifier: ^10.4.2 version: 10.4.2 @@ -306,9 +309,6 @@ catalogs: vscode-languageserver-textdocument: specifier: 1.0.14 version: 1.0.14 - yuku-parser: - specifier: 0.10.2 - version: 0.10.2 importers: @@ -531,15 +531,15 @@ importers: '@rstest/core': specifier: 'catalog:' version: 0.12.0(happy-dom@20.14.5) + '@swc-next/parser': + specifier: 'catalog:' + version: 0.2.2 prettier: specifier: 'catalog:' version: 3.9.7 tinypool: specifier: 'catalog:' version: 2.2.0 - yuku-parser: - specifier: 'catalog:' - version: 0.10.2 devDependencies: '@napi-rs/cli': specifier: 'catalog:' @@ -1852,6 +1852,90 @@ packages: peerDependencies: acorn: ^8.9.0 + '@swc-next/decoder@0.2.2': + resolution: {integrity: sha512-eEGT7Y7StyAQ3M5imF5kkzOJYoiGQZb+CAQ5nLjLVT5npxyaWTiA/3wMYfNpNjQ66eSeCfEQd1mRW8XBhzeEjQ==} + + '@swc-next/parser-binding-darwin-arm64@0.2.2': + resolution: {integrity: sha512-ymR+vu+jmJ0kP6XvfexP3WXFRLaXHEj8bR+coUv5Egk4ujZKjhO4tpKi3wl1S+xrv29DJYlaR+M2EKWeiaA/iQ==} + cpu: [arm64] + os: [darwin] + + '@swc-next/parser-binding-darwin-x64@0.2.2': + resolution: {integrity: sha512-zkVm7R7Jj4xhLIcJAENpSymQviU7ONuBs2XUE3BeSFUDqaCn0d4rQUy7MiopLfPSvMxXcA588cyL+9gpWCb1XA==} + cpu: [x64] + os: [darwin] + + '@swc-next/parser-binding-linux-arm64-gnu@0.2.2': + resolution: {integrity: sha512-mC/u0spVB77KR68Wqj16DF0HTKXac8iNf4TltQhFE1snCy2RZarkxzE1TdL0VP8f8DyuEKe1L4V5F6zFizWDbw==} + cpu: [arm64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-arm64-musl@0.2.2': + resolution: {integrity: sha512-dVUEHAIN1O8iPudJcIKlGmtjKLSueK6zpICOO0q05dLZQExOku2SYNvy+RvciVSVpnHgqIM6TvuXWXLqvjvv4Q==} + cpu: [arm64] + os: [linux] + libc: [musl] + + '@swc-next/parser-binding-linux-ppc64-gnu@0.2.2': + resolution: {integrity: sha512-lzAiv7DsQhPLUCHlLsFsHgcEvTI0LcJoop5gw8i/q1Y+IuBGGJIuwaEQmgSosR40Kr5YUzlDxntlQzq0LT1kBg==} + cpu: [ppc64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-riscv64-gnu@0.2.2': + resolution: {integrity: sha512-2sXJz0/DWUAz4ZPJYZh6c2mNyIJUzWLqagemEqoxHkp48EE8w7UwIoTyaqP72rd/hTSBEC8yO7/LngS4UV8vgA==} + cpu: [riscv64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-riscv64-musl@0.2.2': + resolution: {integrity: sha512-L9BQKEWiJnrJh2JZE9N4FOcOaIP7tnxZtQjNILopQO8q8DkiXjT8Pw/NtCCghffkOqSIkjHZDzfZ1kNqcUO2MQ==} + cpu: [riscv64] + os: [linux] + libc: [musl] + + '@swc-next/parser-binding-linux-s390x-gnu@0.2.2': + resolution: {integrity: sha512-FTsesVJke0yOAS46wNjcV+f4giBnJr+z84gH1lFvTFenf7VV85ujSSPAINX4ydp9SICAlxRUMYcVP4aXqeXn7w==} + cpu: [s390x] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-x64-gnu@0.2.2': + resolution: {integrity: sha512-6CbsMxW+FZWWcItT78bHFQJACstmBRU6wAup4sVlJjxvHi5mM98t2ozhkghob+rwOJLuyW80MClOlyUlpzZJmg==} + cpu: [x64] + os: [linux] + libc: [glibc] + + '@swc-next/parser-binding-linux-x64-musl@0.2.2': + resolution: {integrity: sha512-aNsEqIEPkocxKBMycMUvC687EEIyoEhytzjwbcP4apXCC5ODOHefLD3XnQuqj+Wo+yKtKH/9bbabAGkBAhMWiw==} + cpu: [x64] + os: [linux] + libc: [musl] + + '@swc-next/parser-binding-win32-arm64-msvc@0.2.2': + resolution: {integrity: sha512-rqTCyFCS1MuZJhfeip+01u06kxtO87NQX/j12u8WDZpibIJQSPdJ8N6njjSswSDmMdYSARtBkbKSmnH2a+rSRA==} + cpu: [arm64] + os: [win32] + + '@swc-next/parser-binding-win32-ia32-msvc@0.2.2': + resolution: {integrity: sha512-dR5ghSUFqFqBnpcOWzMFU2g3e2wP9ggSMuhhEu3/GpEM75OmDIsLCzolHygKBhqbqt12NI1XhLwfQvl0/Q2cLA==} + cpu: [ia32] + os: [win32] + + '@swc-next/parser-binding-win32-x64-msvc@0.2.2': + resolution: {integrity: sha512-R79qkJTjlFB0GFTQNZMl7qE9ZXGgF2PyHEKGTQwZJwAOWMSVoN7t6arqI0Wj6T9YSoW+MzW7RH0RitasF6IC/Q==} + cpu: [x64] + os: [win32] + + '@swc-next/parser@0.2.2': + resolution: {integrity: sha512-KpaqY7JD/dUCYvUJz5R7NIIvVIl9qoAQRxFOBhOVukHeVn2DULEvu8o9+IYyFLVZ6CJDnTIONtT1C/ETxC6Pow==} + peerDependencies: + '@swc-next/parser-binding-wasm': 0.2.2 + peerDependenciesMeta: + '@swc-next/parser-binding-wasm': + optional: true + '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -2077,75 +2161,6 @@ packages: peerDependencies: react: '>=18.3.1' - '@yuku-parser/binding-android-arm64@0.10.2': - resolution: {integrity: sha512-2VPBU9fRGRAQ2xPAvghnec3oou5Nrxm2Bezhf+13UMspAxQSkF/32r+ySKXSwIPA5/RivumDJDxAxc301Sgicw==} - cpu: [arm64] - os: [android] - - '@yuku-parser/binding-darwin-arm64@0.10.2': - resolution: {integrity: sha512-LD+PMZE51tCYTOss5HBkm3/AE39MvcMDBfWJx7A4yDcjfNbAQDnHZKtzSOuqrswx+TQHY0ws5xn6+fWOwtmfBA==} - cpu: [arm64] - os: [darwin] - - '@yuku-parser/binding-darwin-x64@0.10.2': - resolution: {integrity: sha512-mmZ8cND+AoIIMRERyMinlg5ApHxP23B9jH2B5wT7T+dliPa9rubLxneB/SUjFwyUjGaFnB5G7t4YvpfbO5zbkQ==} - cpu: [x64] - os: [darwin] - - '@yuku-parser/binding-freebsd-x64@0.10.2': - resolution: {integrity: sha512-gVIjaaIddbRfAhHlC8N809wQWml7mxfSVnzaLtzGXObTeFTPAg/YVnQXt1UOhPM1ah5eG/8Y0RUlNpB4GVe7eQ==} - cpu: [x64] - os: [freebsd] - - '@yuku-parser/binding-linux-arm-gnu@0.10.2': - resolution: {integrity: sha512-q/XPPQQAPdlw05aPj30ygBhekmQryGOwxVraBgApjKK8yY1kNQgqq6XCYLF1WHSee+lhxclrTO7W0/bt7dR1GA==} - cpu: [arm] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm-musl@0.10.2': - resolution: {integrity: sha512-A+Cb0I1hFF4wilTQpWs79k1aNnj4B2tkrHx3zsuUNF9BtVY2zPZ4yeQEM/zjXrS/qJlNMZVK9NrWJjwdpnTrfg==} - cpu: [arm] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-arm64-gnu@0.10.2': - resolution: {integrity: sha512-aGNSzqIqqphFwAdIFwVvKIyXD1Iy3CxrEJVIZAT87Ecyi4vCmmDD2v2l9h+Gd3/wSy3JZlOI792LJbKAjyy9rQ==} - cpu: [arm64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-arm64-musl@0.10.2': - resolution: {integrity: sha512-Ddl1sF0rtuCXyHGSOV6dcmdx+ETv1iD+IVFqQuOjzkJZWclxJxdBWX6ZJMRtTiGqGUTbqLEKiTXxpR/Bvqk+2A==} - cpu: [arm64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-linux-x64-gnu@0.10.2': - resolution: {integrity: sha512-/nlcpR6IF5U+0m5L9wvAIXeQa2DT+BXuvqKDsTcOTlcc0B4dHNyqE5hTXsS4hAyimpy2ZK8A1zMNF5hTrjg2cg==} - cpu: [x64] - os: [linux] - libc: [glibc] - - '@yuku-parser/binding-linux-x64-musl@0.10.2': - resolution: {integrity: sha512-GX80dxTQD/M/OryyuxJcFzFENzry3cDFPvCFTyWogNWQH3fSHfMrNfpwpl1YzMos1eV9NygK5GSDG7VArQlb4w==} - cpu: [x64] - os: [linux] - libc: [musl] - - '@yuku-parser/binding-win32-arm64@0.10.2': - resolution: {integrity: sha512-agePQBV4VHewiGU0ACSjscZ/hJd283f9JfAF19Gf1rI5+wy5tTgx4GS36i0b3xim15AQ4RCpDRosEvhLZ4zAOw==} - cpu: [arm64] - os: [win32] - - '@yuku-parser/binding-win32-x64@0.10.2': - resolution: {integrity: sha512-s8//CMpgL5+y1lvDCdyh1rwGI5+ytDJywFJRe1vnhI7n0j+caxshNucurikYLLVeQ2SYFex6aPrzgQxkabBQRA==} - cpu: [x64] - os: [win32] - - '@yuku-toolchain/types@0.10.2': - resolution: {integrity: sha512-sSeo4SSSToiS+sSD+bwn/s94EEcaLJ7tG5LCp8gFYC1G5VzxzX7fqB6m9RU1yMD8KTpu6zdU/I1NlQf8hVBJ9Q==} - acorn-jsx@5.3.2: resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -3410,12 +3425,6 @@ packages: engines: {node: '>= 14.6'} hasBin: true - yuku-ast@0.10.2: - resolution: {integrity: sha512-UnG9mA6giglCvSErft2/40TVFX750Sj1xgwddPLpG7J7rlr/P1wPADg9G2RK+d/1tNLtRVoLdMMOMVBB9561TQ==} - - yuku-parser@0.10.2: - resolution: {integrity: sha512-CgaU0/PPjCAIEZ3WQroosOxTY3eeKldAN3h+vk8pMNz4+jl1CZzBR4pW+K/rRPF112VooCL5FdjJoiMNjDrL2A==} - zimmerframe@1.1.4: resolution: {integrity: sha512-B58NGBEoc8Y9MWWCQGl/gq9xBCe4IiKM0a2x7GZdQKOW5Exr8S1W24J6OgM1njK8xCRGvAJIL/MxXHf6SkmQKQ==} @@ -4514,6 +4523,65 @@ snapshots: dependencies: acorn: 8.17.0 + '@swc-next/decoder@0.2.2': {} + + '@swc-next/parser-binding-darwin-arm64@0.2.2': + optional: true + + '@swc-next/parser-binding-darwin-x64@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-arm64-gnu@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-arm64-musl@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-ppc64-gnu@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-riscv64-gnu@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-riscv64-musl@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-s390x-gnu@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-x64-gnu@0.2.2': + optional: true + + '@swc-next/parser-binding-linux-x64-musl@0.2.2': + optional: true + + '@swc-next/parser-binding-win32-arm64-msvc@0.2.2': + optional: true + + '@swc-next/parser-binding-win32-ia32-msvc@0.2.2': + optional: true + + '@swc-next/parser-binding-win32-x64-msvc@0.2.2': + optional: true + + '@swc-next/parser@0.2.2': + dependencies: + '@swc-next/decoder': 0.2.2 + optionalDependencies: + '@swc-next/parser-binding-darwin-arm64': 0.2.2 + '@swc-next/parser-binding-darwin-x64': 0.2.2 + '@swc-next/parser-binding-linux-arm64-gnu': 0.2.2 + '@swc-next/parser-binding-linux-arm64-musl': 0.2.2 + '@swc-next/parser-binding-linux-ppc64-gnu': 0.2.2 + '@swc-next/parser-binding-linux-riscv64-gnu': 0.2.2 + '@swc-next/parser-binding-linux-riscv64-musl': 0.2.2 + '@swc-next/parser-binding-linux-s390x-gnu': 0.2.2 + '@swc-next/parser-binding-linux-x64-gnu': 0.2.2 + '@swc-next/parser-binding-linux-x64-musl': 0.2.2 + '@swc-next/parser-binding-win32-arm64-msvc': 0.2.2 + '@swc-next/parser-binding-win32-ia32-msvc': 0.2.2 + '@swc-next/parser-binding-win32-x64-msvc': 0.2.2 + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -4684,44 +4752,6 @@ snapshots: react: 19.3.0 unhead: 2.1.17 - '@yuku-parser/binding-android-arm64@0.10.2': - optional: true - - '@yuku-parser/binding-darwin-arm64@0.10.2': - optional: true - - '@yuku-parser/binding-darwin-x64@0.10.2': - optional: true - - '@yuku-parser/binding-freebsd-x64@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm-gnu@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm-musl@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm64-gnu@0.10.2': - optional: true - - '@yuku-parser/binding-linux-arm64-musl@0.10.2': - optional: true - - '@yuku-parser/binding-linux-x64-gnu@0.10.2': - optional: true - - '@yuku-parser/binding-linux-x64-musl@0.10.2': - optional: true - - '@yuku-parser/binding-win32-arm64@0.10.2': - optional: true - - '@yuku-parser/binding-win32-x64@0.10.2': - optional: true - - '@yuku-toolchain/types@0.10.2': {} - acorn-jsx@5.3.2(acorn@8.17.0): dependencies: acorn: 8.17.0 @@ -6255,28 +6285,6 @@ snapshots: yaml@2.9.0: optional: true - yuku-ast@0.10.2: - dependencies: - '@yuku-toolchain/types': 0.10.2 - - yuku-parser@0.10.2: - dependencies: - '@yuku-toolchain/types': 0.10.2 - yuku-ast: 0.10.2 - optionalDependencies: - '@yuku-parser/binding-android-arm64': 0.10.2 - '@yuku-parser/binding-darwin-arm64': 0.10.2 - '@yuku-parser/binding-darwin-x64': 0.10.2 - '@yuku-parser/binding-freebsd-x64': 0.10.2 - '@yuku-parser/binding-linux-arm-gnu': 0.10.2 - '@yuku-parser/binding-linux-arm-musl': 0.10.2 - '@yuku-parser/binding-linux-arm64-gnu': 0.10.2 - '@yuku-parser/binding-linux-arm64-musl': 0.10.2 - '@yuku-parser/binding-linux-x64-gnu': 0.10.2 - '@yuku-parser/binding-linux-x64-musl': 0.10.2 - '@yuku-parser/binding-win32-arm64': 0.10.2 - '@yuku-parser/binding-win32-x64': 0.10.2 - zimmerframe@1.1.4: {} zwitch@2.0.4: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 76d7cbbe..5713e7b6 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -28,6 +28,7 @@ catalog: '@rstest/adapter-rsbuild': '~0.12.0' '@rstest/adapter-rslib': '~0.12.0' '@rstest/core': '~0.12.0' + '@swc-next/parser': '0.2.2' '@testing-library/dom': '^10.4.2' '@testing-library/jest-dom': '^7.0.1' '@testing-library/react': '^16.3.3' @@ -59,7 +60,6 @@ catalog: 'typescript': '^7.0.2' 'vscode-languageserver': '10.1.1' 'vscode-languageserver-textdocument': '1.0.14' - yuku-parser: '0.10.2' dedupePeers: true @@ -72,21 +72,7 @@ hoistPattern: [] # Reduce the risk of installing compromised packages minimumReleaseAge: 1440 minimumReleaseAgeExclude: - - '@yuku-parser/binding-android-arm64@0.8.3' - - '@yuku-parser/binding-darwin-arm64@0.8.3' - - '@yuku-parser/binding-darwin-x64@0.8.3' - - '@yuku-parser/binding-freebsd-x64@0.8.3' - - '@yuku-parser/binding-linux-arm-gnu@0.8.3' - - '@yuku-parser/binding-linux-arm-musl@0.8.3' - - '@yuku-parser/binding-linux-arm64-gnu@0.8.3' - - '@yuku-parser/binding-linux-arm64-musl@0.8.3' - - '@yuku-parser/binding-linux-x64-gnu@0.8.3' - - '@yuku-parser/binding-linux-x64-musl@0.8.3' - - '@yuku-parser/binding-win32-arm64@0.8.3' - - '@yuku-parser/binding-win32-x64@0.8.3' - - '@yuku-toolchain/types@0.8.3' - - 'yuku-ast@0.8.3' - - 'yuku-parser@0.8.3' + - '@swc-next/*' - '@rspack/*' - '@rsbuild/*' - '@rslib/*' diff --git a/website/docs/en/guide/formatting.mdx b/website/docs/en/guide/formatting.mdx index 1bb65218..02563027 100644 --- a/website/docs/en/guide/formatting.mdx +++ b/website/docs/en/guide/formatting.mdx @@ -9,7 +9,7 @@ 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: - **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 high-performance [SWC Next](https://www.npmjs.com/package/@swc-next/parser) 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 da96317c..213abed9 100644 --- a/website/docs/zh/guide/formatting.mdx +++ b/website/docs/zh/guide/formatting.mdx @@ -9,7 +9,7 @@ import { PackageManagerTabs } from '@rspress/core/theme'; Rstack CLI 提供了基于 [Prettier](https://prettier.io/) 的格式化工具。相比直接使用 Prettier,`rs fmt` 的性能更好,主要得益于以下两点: - **并行格式化**:通过 worker 池并行格式化文件。 -- **Yuku 解析器**:默认使用高性能的 [Yuku](https://yuku.fyi/) 解析器处理 JavaScript、JSX 和 TypeScript 文件。 +- **SWC Next 解析器**:默认使用高性能的 [SWC Next](https://www.npmjs.com/package/@swc-next/parser) 解析器处理 JavaScript、JSX 和 TypeScript 文件。 - **持久化缓存**:基于文件内容缓存结果,后续运行可以跳过未变化文件的格式化。 `rs fmt` 兼容 Prettier 的选项和插件,并提供更多内置能力,例如支持[排序 package.json 字段](#sort-package-json)。