diff --git a/Cargo.lock b/Cargo.lock index babdf52..b924487 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -11,6 +11,12 @@ dependencies = [ "memchr", ] +[[package]] +name = "allocator-api2" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923" + [[package]] name = "bitflags" version = "2.13.1" @@ -27,6 +33,15 @@ dependencies = [ "serde_core", ] +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" +dependencies = [ + "allocator-api2", +] + [[package]] name = "cfg-if" version = "1.0.4" @@ -73,6 +88,18 @@ version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "914a755b7c2d4af2bdcff7ce1739e2db9a1b81a9b07123d8015786ae03c0980d" +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "futures" version = "0.3.34" @@ -174,6 +201,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +dependencies = [ + "allocator-api2", + "equivalent", + "foldhash", +] + [[package]] name = "ignore" version = "0.4.33" @@ -190,6 +228,12 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "libc" version = "0.2.189" @@ -282,6 +326,21 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2bf50223579dc7cdcfb3bfcacf7069ff68243f8c363f62ffa99cf000a6b9c451" +[[package]] +name = "nonmax" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "610a5acd306ec67f907abe5567859a3c693fb9886eb1f012ab8f2a47bef3db51" + +[[package]] +name = "oxc_index" +version = "5.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "191884bee6c3744909a51acc7d78d4ae370d817b25875b10642f632327b6296e" +dependencies = [ + "nonmax", +] + [[package]] name = "pathdiff" version = "0.2.3" @@ -337,6 +396,7 @@ dependencies = [ "napi-build", "napi-derive", "rstack-ignore", + "swc_next_bridge", ] [[package]] @@ -368,6 +428,16 @@ version = "1.0.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" +[[package]] +name = "serde" +version = "1.0.229" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4148590afebada386688f18773da617792bf2ef03ffc1e4cbd2b1d45b023e0ba" +dependencies = [ + "serde_core", + "serde_derive", +] + [[package]] name = "serde_core" version = "1.0.229" @@ -388,12 +458,101 @@ dependencies = [ "syn 3.0.4", ] +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + [[package]] name = "slab" version = "0.4.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" +[[package]] +name = "swc_next_allocator" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72965c23b599e53e7dc304d56a8e2d5c971a82738920ba3e303b9799a26c1a75" +dependencies = [ + "allocator-api2", + "bumpalo", + "hashbrown", + "rustc-hash", +] + +[[package]] +name = "swc_next_ast_macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e3570bd93a03c61564002d421ffa93f9ff62a9817a333cd8e340939ef1c8ce3" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "swc_next_bridge" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c44e850cd73ca946731ae31fa88d12e3893b8f01f119279231b168088d5bbb79" +dependencies = [ + "napi", + "napi-build", + "napi-derive", + "swc_next_allocator", + "swc_next_bridge_macros", + "swc_next_ecma_ast", + "swc_next_ecma_parser", +] + +[[package]] +name = "swc_next_bridge_macros" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2c57c5afb5a2baf6647a9cfcb667b857ca1226c8066bb9a44331a7401916f5f" +dependencies = [ + "proc-macro2", + "quote", + "serde", + "serde_json", + "syn 2.0.119", +] + +[[package]] +name = "swc_next_ecma_ast" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee81510203d325d7272f4f348b87813258e139e5931ffc1720915ca792b3e6ae" +dependencies = [ + "oxc_index", + "swc_next_allocator", + "swc_next_ast_macros", + "unicode-id-start", +] + +[[package]] +name = "swc_next_ecma_parser" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1d1f170e4cb99604e0cfebcc48cc3e10040f1df91921c46f8126e72ae41df61" +dependencies = [ + "bitflags", + "memchr", + "rustc-hash", + "swc_next_allocator", + "swc_next_ecma_ast", +] + [[package]] name = "syn" version = "2.0.119" @@ -416,6 +575,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "unicode-id-start" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81b79ad29b5e19de4260020f8919b443b2ef0277d242ce532ec7b7a2cc8b6007" + [[package]] name = "unicode-ident" version = "1.0.24" @@ -461,3 +626,9 @@ checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" dependencies = [ "windows-link", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/Cargo.toml b/Cargo.toml index 29cf6bd..68f9e91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,6 +14,8 @@ napi-build = "2.4.1" napi-derive = "3.6.3" pathdiff = "0.2.3" rstack-ignore = { path = "crates/rstack-ignore" } +# The wire format must match @swc-next/decoder exactly. +swc_next_bridge = { version = "=0.2.0", features = ["parser"] } # Local development: 16 codegen units are sufficient for this small workspace while preserving # parallel compilation and full debugging support. diff --git a/crates/rstack-binding/Cargo.toml b/crates/rstack-binding/Cargo.toml index a1b861a..7e305ee 100644 --- a/crates/rstack-binding/Cargo.toml +++ b/crates/rstack-binding/Cargo.toml @@ -14,6 +14,7 @@ test = false napi.workspace = true napi-derive.workspace = true rstack-ignore.workspace = true +swc_next_bridge.workspace = true [build-dependencies] napi-build.workspace = true diff --git a/crates/rstack-binding/src/lib.rs b/crates/rstack-binding/src/lib.rs index 68596e1..0626878 100644 --- a/crates/rstack-binding/src/lib.rs +++ b/crates/rstack-binding/src/lib.rs @@ -1,5 +1,9 @@ #![deny(clippy::all)] +mod parser; + +pub use parser::parse_swc_next; + use std::path::Path; use napi::{bindgen_prelude::Uint8Array, Error, Status}; diff --git a/crates/rstack-binding/src/parser.rs b/crates/rstack-binding/src/parser.rs new file mode 100644 index 0000000..888f77a --- /dev/null +++ b/crates/rstack-binding/src/parser.rs @@ -0,0 +1,43 @@ +use napi::{bindgen_prelude::Buffer, Error, Status}; +use napi_derive::napi; +use swc_next_bridge::{parse_to_buffer, CommentMode, Lang, ParserOptions, SourceType}; + +/// Returns the SWC Next 0.2.0 wire format consumed by @swc-next/decoder. +/// Keep parsing and serialization in the upstream crate, not in the NAPI adapter. +#[napi] +pub fn parse_swc_next(source: String, lang: String, source_type: String) -> napi::Result { + let lang = match lang.as_str() { + "js" => Lang::Js, + "jsx" => Lang::Jsx, + "ts" => Lang::Ts, + "tsx" => Lang::Tsx, + "dts" => Lang::Dts, + _ => { + return Err(Error::new( + Status::InvalidArg, + "Unsupported SWC Next language.", + )) + } + }; + let source_type = match source_type.as_str() { + "module" => SourceType::Module, + "commonjs" => SourceType::CommonJs, + _ => { + return Err(Error::new( + Status::InvalidArg, + "Unsupported SWC Next source type.", + )) + } + }; + + Ok(parse_to_buffer( + &source, + Some(ParserOptions { + lang: Some(lang), + source_type: Some(source_type), + preserve_parens: Some(true), + comments: Some(CommentMode::Flat), + }), + ) + .into()) +} diff --git a/packages/rstack/SWC_NEXT_POC.md b/packages/rstack/SWC_NEXT_POC.md new file mode 100644 index 0000000..b71bf6b --- /dev/null +++ b/packages/rstack/SWC_NEXT_POC.md @@ -0,0 +1,32 @@ +# 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_bridge = "=0.2.0"` compiles parsing and serialization into the existing rstack native binding. A thin NAPI function returns a Buffer to `@swc-next/decoder@0.2.0`; the generated loader is resolved lazily when parsing starts. No parser npm package or separate SWC Next native binary is installed. + +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 +``` + +Native changes also require `cargo fmt --all -- --check`, `cargo clippy --workspace --all-targets --locked -- -D warnings`, and `cargo test --workspace --locked`. + +## 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 a4f4fa3..4948fef 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/binding.cjs b/packages/rstack/binding.cjs index eec2547..593e66b 100644 --- a/packages/rstack/binding.cjs +++ b/packages/rstack/binding.cjs @@ -702,3 +702,7 @@ if (!nativeBinding) { module.exports = nativeBinding module.exports.GitIgnoreMatcher = nativeBinding.GitIgnoreMatcher module.exports.IgnoreMatcher = nativeBinding.IgnoreMatcher +module.exports.parseSwcNext = nativeBinding.parseSwcNext +module.exports.CommentMode = nativeBinding.CommentMode +module.exports.Lang = nativeBinding.Lang +module.exports.SourceType = nativeBinding.SourceType diff --git a/packages/rstack/binding.d.cts b/packages/rstack/binding.d.cts index d5fb734..94f5f32 100644 --- a/packages/rstack/binding.d.cts +++ b/packages/rstack/binding.d.cts @@ -37,3 +37,59 @@ export interface IgnoreSource { /** Newline-delimited Gitignore patterns. */ patterns: string } + +/** + * Returns the SWC Next 0.2.0 wire format consumed by @swc-next/decoder. + * Keep parsing and serialization in the upstream crate, not in the NAPI adapter. + */ +export declare function parseSwcNext(source: string, lang: string, sourceType: string): Buffer +export declare const enum CommentMode { + None = 'none', + Flat = 'flat' +} + +export declare const enum Lang { + Js = 'js', + Ts = 'ts', + Jsx = 'jsx', + Tsx = 'tsx', + Dts = 'dts' +} + +export interface ParserOptions { + /** + * Determines how the source is parsed and evaluated. + * + * @default "module" + */ + sourceType?: SourceType + /** + * Determines which syntax features are enabled. + * + * @default "js" + */ + lang?: Lang + /** + * Preserve parenthesized expressions and TypeScript types as explicit + * parenthesized nodes. + * + * @default true + */ + preserveParens?: boolean + /** + * Determines how comments are exposed. + * + * `"none"` disables comment collection. `"flat"` exposes a + * source-ordered comment list. + * + * @default "flat" + */ + comments?: CommentMode +} + +export declare const enum SourceType { + Module = 'module', + Script = 'script', + CommonJs = 'commonjs', + Unambiguous = 'unambiguous' +} diff --git a/packages/rstack/package.json b/packages/rstack/package.json index 0f8aed3..c045920 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/decoder": "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 81d7bdd..1780153 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:native', ]); /** 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 5dc633f..9c3cb2b 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 0000000..f35959e --- /dev/null +++ b/packages/rstack/src/fmt/swcNextParser.ts @@ -0,0 +1,36 @@ +import { decode, type DecodeResult } from '@swc-next/decoder'; +import { loadNativeBinding } from '../native/index.ts'; + +export type { Comment, Diagnostic } from '@swc-next/decoder'; +export type Lang = 'js' | 'jsx' | 'ts' | 'tsx' | 'dts'; +export type SourceType = 'module' | 'commonjs'; +export type ParseOptions = { + lang: Lang; + sourceType: SourceType; + preserveParens: true; + comments: 'flat'; +}; + +export const langFromPath = (filePath: string): Lang => { + if (/\.d\.(?:ts|mts|cts)$/i.test(filePath)) return 'dts'; + if (/\.tsx$/i.test(filePath)) return 'tsx'; + if (/\.(?:ts|mts|cts)$/i.test(filePath)) return 'ts'; + if (/\.jsx$/i.test(filePath)) return 'jsx'; + return 'js'; +}; + +// Keep the upstream untyped AST behind an unknown boundary. +export type ParseResult = Omit & { program: unknown }; + +export const parseSync = ( + source: string, + options: ParseOptions, +): ParseResult => { + // Resolve the generated loader only when formatting actually needs a parser. + const buffer = loadNativeBinding().parseSwcNext( + source, + options.lang, + options.sourceType, + ); + return decode(buffer, source); +}; 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 bd6d74f..932fe08 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 29f2beb..6d0cd4f 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:native', ]); }); diff --git a/packages/rstack/tests/fmt/swcNextNative.test.ts b/packages/rstack/tests/fmt/swcNextNative.test.ts new file mode 100644 index 0000000..6b59e50 --- /dev/null +++ b/packages/rstack/tests/fmt/swcNextNative.test.ts @@ -0,0 +1,31 @@ +import { decode } from '@swc-next/decoder'; +import { expect, test } from 'rstack/test'; +import { loadNativeBinding } from '../../src/native/index.ts'; + +test('decodes owned native buffers and UTF-16 diagnostics across repeated parses', () => { + const binding = loadNativeBinding(); + const validSource = '// 中文 😀\nconst value = 1;'; + const validBuffer = binding.parseSwcNext(validSource, 'ts', 'module'); + const invalidSource = 'const text = "😀"; const = 1;'; + const invalidBuffer = binding.parseSwcNext(invalidSource, 'ts', 'module'); + + expect(Buffer.isBuffer(validBuffer)).toBe(true); + const valid = decode(validBuffer, validSource); + expect(valid.diagnostics).toEqual([]); + expect(valid.comments).toEqual([ + { type: 'Line', value: ' 中文 😀', start: 0, end: 8 }, + ]); + const invalid = decode(invalidBuffer, invalidSource); + expect(invalid.diagnostics[0].start).toBe(invalidSource.lastIndexOf('=')); + expect(invalid.diagnostics[0].end).toBe(invalidSource.lastIndexOf('=') + 1); +}); + +test('rejects unsupported native parser options', () => { + const binding = loadNativeBinding(); + expect(() => binding.parseSwcNext('', 'unknown', 'module')).toThrow( + 'Unsupported SWC Next language.', + ); + expect(() => binding.parseSwcNext('', 'ts', 'unknown')).toThrow( + 'Unsupported SWC Next source type.', + ); +}); 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 59e7e39..59cc3d5 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 41a4388..99bc92a 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/decoder': + 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/decoder': + 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,9 @@ packages: peerDependencies: acorn: ^8.9.0 + '@swc-next/decoder@0.2.0': + resolution: {integrity: sha512-1VG4sU5uzhLTrHPNELAqMY5+ek1RnHfwgCBSlZ0PfXy08yIGQ0hCluSSnXgPoJ3Iq5qIWktzSG6RF4GAGbSFRw==} + '@swc/helpers@0.5.23': resolution: {integrity: sha512-5lSsMOTXURePglDfvuAQUqkGek9Hg2kksOYay2m0+XR++b2NWYL/4sWyuvVBIs8oKnJaxkdi9whaL/sqN13afw==} @@ -1827,75 +1830,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 +3088,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 +4098,8 @@ snapshots: dependencies: acorn: 8.17.0 + '@swc-next/decoder@0.2.0': {} + '@swc/helpers@0.5.23': dependencies: tslib: 2.8.1 @@ -4340,44 +4270,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 +5799,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 b8d1160..0e858cf 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/decoder': '0.2.0' dedupePeers: true @@ -72,6 +72,7 @@ hoistPattern: [] # Reduce the risk of installing compromised packages minimumReleaseAge: 1440 minimumReleaseAgeExclude: + - '@swc-next/decoder@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 123048f..49e9f96 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 d1067e6..9539354 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)。