Skip to content

Commit 126694f

Browse files
authored
feat(fmt): use SWC Next parser (#464)
1 parent b0968cc commit 126694f

10 files changed

Lines changed: 300 additions & 248 deletions

File tree

‎.github/renovate.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@
1010
"@rslib/core",
1111
"@rslint/core",
1212
"@rstest/core",
13-
"prettier",
14-
"yuku-parser"
13+
"@swc-next/parser",
14+
"prettier"
1515
],
1616
"groupName": null,
1717
"groupSlug": null,

‎packages/rstack/THIRD_PARTY_NOTICES.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,9 +29,9 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
2929
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
3030
SOFTWARE.
3131

32-
## Prettier yuku parser adapter
32+
## Prettier SWC next parser adapter
3333

34-
The local Yuku parser adapter includes portions derived from
34+
The local SWC Next parser adapter includes portions derived from
3535
[@prettier/plugin-yuku](https://github.com/prettier/prettier/tree/main/packages/plugin-yuku)
3636
and Prettier's JavaScript parser postprocessing. The adapter reuses the public
3737
ESTree printer, formatter options, and parser utilities from the installed

‎packages/rstack/package.json‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,9 @@
7777
"@rslib/core": "catalog:",
7878
"@rslint/core": "catalog:",
7979
"@rstest/core": "catalog:",
80+
"@swc-next/parser": "catalog:",
8081
"prettier": "catalog:",
81-
"tinypool": "catalog:",
82-
"yuku-parser": "catalog:"
82+
"tinypool": "catalog:"
8383
},
8484
"devDependencies": {
8585
"@napi-rs/cli": "catalog:",

‎packages/rstack/src/fmt/prettierPlugins.ts‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import type { Options as PrettierOptions, Plugin } from 'prettier';
22
import type { ResolvedFmtOptions } from './types.ts';
3-
import { yukuPlugin } from './yukuPlugin.ts';
3+
import { swcNextPlugin } from './swcNextPlugin.ts';
44

55
type PrettierPlugins = NonNullable<PrettierOptions['plugins']>;
66

@@ -15,14 +15,14 @@ const fmtOptionsPlugin = {
1515
},
1616
} satisfies Plugin;
1717

18-
const defaultFmtPlugins: PrettierPlugins = [yukuPlugin, fmtOptionsPlugin];
18+
const defaultFmtPlugins: PrettierPlugins = [swcNextPlugin, fmtOptionsPlugin];
1919

2020
/** Prepends bundled plugins so project plugins can override their parsers. */
2121
const getPrettierPlugins = async (
2222
options: ResolvedFmtOptions,
2323
filePath: string,
2424
): Promise<PrettierPlugins> => {
25-
// An explicit native parser is also the escape hatch for bypassing Yuku.
25+
// An explicit native parser is also the escape hatch for bypassing SWC Next.
2626
const defaultPlugins =
2727
options.parser === 'babel' || options.parser === 'typescript'
2828
? [fmtOptionsPlugin]
Lines changed: 55 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,26 @@
1+
// Adapted from @prettier/plugin-yuku to use SWC Next:
2+
// https://github.com/prettier/prettier/tree/main/packages/plugin-yuku
3+
14
import * as prettierEstreePlugin from 'prettier/plugins/estree';
25
import type { Parser, ParserOptions, Plugin } from 'prettier';
36
import {
7+
CommentMode,
8+
Lang,
9+
SourceType,
410
langFromPath,
5-
parse as parseWithYuku,
11+
parseSync as parseWithSwcNext,
612
type Comment,
713
type Diagnostic,
8-
type ParseOptions,
914
type ParseResult,
10-
type SourceLang,
11-
type SourceType,
12-
} from 'yuku-parser';
15+
} from '@swc-next/parser';
1316

14-
const AST_FORMAT = 'estree-yuku';
17+
const AST_FORMAT = 'estree-swc-next';
1518
const JS_TS_FILE_REGEXP = /\.(?:js|mjs|cjs|jsx|ts|mts|cts|tsx)$/i;
1619
const JSX_REGEXP = /^[^"'`]*<\/|^[^/]{2}.*\/>/m;
17-
const SOURCE_TYPE_COMBINATIONS: SourceType[] = ['module', 'commonjs'];
20+
const SOURCE_TYPE_COMBINATIONS: SourceType[] = [
21+
SourceType.Module,
22+
SourceType.CommonJs,
23+
];
1824

1925
type Range = [start: number, end: number];
2026

@@ -158,7 +164,7 @@ const isAstNode = (value: unknown): value is AstNode =>
158164

159165
const asAstNode = (value: unknown): AstNode => {
160166
if (!isAstNode(value)) {
161-
throw new TypeError('Expected a Yuku AST node.');
167+
throw new TypeError('Expected a SWC Next AST node.');
162168
}
163169
return value;
164170
};
@@ -220,7 +226,7 @@ const stripComments = (
220226
const chunks: string[] = [];
221227
let cursor = 0;
222228

223-
// Yuku returns comments in source order, so mask each range while copying the source only once.
229+
// SWC Next returns comments in source order, so mask each range while copying the source only once.
224230
for (const comment of comments) {
225231
const start = locStart(comment);
226232
const end = locEnd(comment);
@@ -356,7 +362,7 @@ const postprocess = (
356362
ast: AstNode,
357363
comments: PrettierComment[],
358364
text: string,
359-
astType: 'yuku-js' | 'yuku-ts',
365+
astType: 'swc-next-js' | 'swc-next-ts',
360366
): AstNode => {
361367
mergeNestedJsdocComments(comments);
362368

@@ -384,7 +390,7 @@ const postprocess = (
384390
const expression = asAstNode(node.expression);
385391
const start = locStart(node);
386392

387-
// Yuku comments are in source order, so these end offsets are sorted.
393+
// SWC Next comments are in source order, so these end offsets are sorted.
388394
typeCastCommentEnds ??= comments
389395
.filter(isTypeCastComment)
390396
.map((comment) => locEnd(comment));
@@ -415,7 +421,7 @@ const postprocess = (
415421
}
416422

417423
case 'TemplateElement': {
418-
if (astType === 'yuku-ts') {
424+
if (astType === 'swc-next-ts') {
419425
const start = locStart(node) + 1;
420426
const end = locEnd(node) - (node.tail ? 1 : 2);
421427
node.range = [start, end];
@@ -482,11 +488,13 @@ const createParseError = (error: Diagnostic, text: string): SyntaxError => {
482488
);
483489
};
484490

485-
const parseWithOptions = (text: string, options: ParseOptions): ParseResult => {
486-
const result = parseWithYuku(text, {
491+
const parseWithOptions = (
492+
text: string,
493+
options: { sourceType: SourceType; lang: Lang },
494+
): ParseResult => {
495+
const result = parseWithSwcNext(text, {
487496
preserveParens: true,
488-
semanticErrors: false,
489-
attachComments: false,
497+
comments: CommentMode.Flat,
490498
...options,
491499
});
492500

@@ -499,28 +507,27 @@ const parseWithOptions = (text: string, options: ParseOptions): ParseResult => {
499507

500508
const getSourceType = (filepath: string): SourceType | undefined => {
501509
if (/\.(?:mjs|mts)$/i.test(filepath)) {
502-
return 'module';
510+
return SourceType.Module;
503511
}
504512

505513
if (/\.(?:cjs|cts)$/i.test(filepath)) {
506-
return 'commonjs';
514+
return SourceType.CommonJs;
507515
}
508516

509517
return undefined;
510518
};
511519

512-
const getLanguageCombinations = (
513-
text: string,
514-
filepath: string,
515-
): SourceLang[] => {
520+
const getLanguageCombinations = (text: string, filepath: string): Lang[] => {
516521
const normalizedPath = filepath.toLowerCase();
517522

518523
if (JS_TS_FILE_REGEXP.test(normalizedPath)) {
519524
return [langFromPath(normalizedPath)];
520525
}
521526

522527
// Embedded code from Vue or Svelte keeps the host file path, so detect JSX from its content.
523-
return JSX_REGEXP.test(text) ? ['tsx', 'ts', 'dts'] : ['ts', 'tsx', 'dts'];
528+
return JSX_REGEXP.test(text)
529+
? [Lang.Tsx, Lang.Ts, Lang.Dts]
530+
: [Lang.Ts, Lang.Tsx, Lang.Dts];
524531
};
525532

526533
const tryCombinations = (combinations: (() => ParseResult)[]): ParseResult => {
@@ -542,7 +549,7 @@ const tryCombinations = (combinations: (() => ParseResult)[]): ParseResult => {
542549
throw firstError;
543550
}
544551

545-
throw new Error('No Yuku parser combinations were provided.');
552+
throw new Error('No SWC Next parser combinations were provided.');
546553
};
547554

548555
const parseJavaScript = (
@@ -554,11 +561,16 @@ const parseJavaScript = (
554561
sourceType ? [sourceType] : SOURCE_TYPE_COMBINATIONS
555562
).map(
556563
(candidate) => () =>
557-
parseWithOptions(text, { sourceType: candidate, lang: 'jsx' }),
564+
parseWithOptions(text, { sourceType: candidate, lang: Lang.Jsx }),
558565
);
559-
const { program, comments } = tryCombinations(combinations);
566+
const result = tryCombinations(combinations);
560567

561-
return postprocess(program as unknown as AstNode, comments, text, 'yuku-js');
568+
return postprocess(
569+
asAstNode(result.program),
570+
result.comments,
571+
text,
572+
'swc-next-js',
573+
);
562574
};
563575

564576
const parseTypeScript = (
@@ -574,9 +586,14 @@ const parseTypeScript = (
574586
(lang) => () => parseWithOptions(text, { sourceType: candidate, lang }),
575587
),
576588
);
577-
const { program, comments } = tryCombinations(combinations);
589+
const result = tryCombinations(combinations);
578590

579-
return postprocess(program as unknown as AstNode, comments, text, 'yuku-ts');
591+
return postprocess(
592+
asAstNode(result.program),
593+
result.comments,
594+
text,
595+
'swc-next-ts',
596+
);
580597
};
581598

582599
const createParser = (
@@ -611,23 +628,23 @@ const parseBabel = (text: string, options: ParserOptions<AstNode>): AstNode => {
611628
};
612629
};
613630

614-
const yukuParser = createParser(parseJavaScript);
615-
const yukuBabelParser = createParser(parseBabel);
616-
const yukuTypeScriptParser = createParser(parseTypeScript);
631+
const swcNextParser = createParser(parseJavaScript);
632+
const swcNextBabelParser = createParser(parseBabel);
633+
const swcNextTypeScriptParser = createParser(parseTypeScript);
617634

618-
const yukuPlugin: Plugin = {
635+
const swcNextPlugin: Plugin = {
619636
options: estreePlugin.options,
620637
parsers: {
621638
// Prettier resolves parsers from the last plugin that provides the name.
622639
// Project plugins are loaded after this one, so parser wrappers take priority.
623-
babel: yukuBabelParser,
624-
typescript: yukuTypeScriptParser,
625-
yuku: yukuParser,
626-
'yuku-ts': yukuTypeScriptParser,
640+
babel: swcNextBabelParser,
641+
typescript: swcNextTypeScriptParser,
642+
'swc-next': swcNextParser,
643+
'swc-next-ts': swcNextTypeScriptParser,
627644
},
628645
printers: {
629646
[AST_FORMAT]: estreePrinter,
630647
},
631648
};
632649

633-
export { yukuPlugin };
650+
export { swcNextPlugin };

0 commit comments

Comments
 (0)