Skip to content

Commit 4c588fb

Browse files
committed
feat: add Parser.mergePlugins/removePlugins/for(Js/Ts/Tsx/Dts/Extension)
BREAKING CHANGE: the default parser options no longer include features that are still proposals like decorators, doExpressions, exportDefaultFrom, functionBind, pipelineOperator, and throwExpressions.
1 parent 291346f commit 4c588fb

File tree

3 files changed

+128
-60
lines changed

3 files changed

+128
-60
lines changed

src/index.js.flow

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// @flow
22

3-
type ParserOptions = any
3+
type ParserOptions = { ... }
4+
type ParserPlugin = string | [string, { ... }]
45
type File = any
56
type Expression = any
67

@@ -19,12 +20,22 @@ declare export class Parser {
1920
parseExpression(code: string, parserOpts?: ParserOptions): Expression;
2021

2122
bindParserOpts(parserOpts: ParserOptions): Parser;
23+
24+
mergePlugins(...plugins: ParserPlugin[]): Parser;
25+
removePlugins(...plugins: string[]): Parser;
26+
27+
get forJs(): Parser;
28+
get forTs(): Parser;
29+
get forTsx(): Parser;
30+
get forDts(): Parser;
31+
32+
forExtension(e: string): Parser;
2233
}
2334

35+
declare export var jsParser: Parser
2436
declare export var tsParser: Parser
25-
declare export var dtsParser: Parser
2637
declare export var tsxParser: Parser
27-
declare export var jsParser: Parser
38+
declare export var dtsParser: Parser
2839

2940
declare export function clearCache(): void
3041
declare export function getParserSync(

src/index.ts

Lines changed: 112 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,30 @@ function isEmpty(obj: any): boolean {
1515

1616
type BabelParser = Pick<typeof defaultBabelParser, 'parse' | 'parseExpression'>
1717

18+
function pluginName(p: ParserPlugin): string {
19+
return typeof p === 'string' ? p : p[0]
20+
}
21+
22+
function pluginOpts(p: ParserPlugin): any {
23+
return typeof p === 'string' ? {} : p[1]
24+
}
25+
26+
function arePluginsEqual(a: ParserPlugin, b: ParserPlugin): boolean {
27+
return (
28+
pluginName(a) === pluginName(b) &&
29+
t.shallowEqual(pluginOpts(a), pluginOpts(b))
30+
)
31+
}
32+
1833
function mergePlugins(
1934
a: ParserPlugin[] | undefined,
2035
b: ParserPlugin[] | undefined
2136
): ParserPlugin[] | undefined {
2237
if (!b) return a
2338
if (!a) return b
39+
40+
if (b.every((bp) => a.find((ap) => arePluginsEqual(ap, bp)))) return a
41+
2442
// eslint-disable-next-line @typescript-eslint/no-explicit-any
2543
const map: Map<string, any> = new Map(
2644
// eslint-disable-next-line @typescript-eslint/no-explicit-any
@@ -39,6 +57,16 @@ function mergePlugins(
3957
) as any
4058
}
4159

60+
function removePlugins(
61+
a: ParserPlugin[] | undefined,
62+
b: string[]
63+
): ParserPlugin[] | undefined {
64+
if (!b.some((plugin) => a?.some((p) => pluginName(p) === plugin))) {
65+
return a
66+
}
67+
return a?.filter((p) => !b.includes(pluginName(p)))
68+
}
69+
4270
export class Parser {
4371
readonly babelParser: BabelParser
4472
readonly parserOpts: ParserOptions
@@ -67,50 +95,83 @@ export class Parser {
6795
plugins: mergePlugins(this.parserOpts.plugins, parserOpts.plugins),
6896
})
6997
}
98+
99+
mergePlugins(...plugins: ParserPlugin[]): Parser {
100+
const merged = mergePlugins(this.parserOpts.plugins, plugins)
101+
return merged === this.parserOpts.plugins
102+
? this
103+
: new Parser(this.babelParser, {
104+
...this.parserOpts,
105+
plugins: merged,
106+
})
107+
}
108+
109+
removePlugins(...plugins: string[]): Parser {
110+
const removed = removePlugins(this.parserOpts.plugins, plugins)
111+
return removed === this.parserOpts.plugins
112+
? this
113+
: new Parser(this.babelParser, {
114+
...this.parserOpts,
115+
plugins: removed,
116+
})
117+
}
118+
119+
get forJs(): Parser {
120+
if (!this.parserOpts.plugins?.some((p) => pluginName(p) === 'typescript'))
121+
return this
122+
return this.removePlugins('typescript', 'decorators-legacy').mergePlugins(
123+
['flow', { all: true }],
124+
'flowComments',
125+
'jsx',
126+
['decorators', { decoratorsBeforeExport: false }]
127+
)
128+
}
129+
130+
get forTs(): Parser {
131+
return this.removePlugins(
132+
'flow',
133+
'flowComments',
134+
'decorators',
135+
'jsx'
136+
).mergePlugins(
137+
['typescript', { disallowAmbiguousJSXLike: true, dts: false }],
138+
'decorators-legacy'
139+
)
140+
}
141+
142+
get forTsx(): Parser {
143+
return this.removePlugins(
144+
'flow',
145+
'flowComments',
146+
'decorators'
147+
).mergePlugins(
148+
['typescript', { disallowAmbiguousJSXLike: true, dts: false }],
149+
'decorators-legacy',
150+
'jsx'
151+
)
152+
}
153+
154+
get forDts(): Parser {
155+
return this.removePlugins(
156+
'flow',
157+
'flowComments',
158+
'decorators',
159+
'jsx'
160+
).mergePlugins(
161+
['typescript', { disallowAmbiguousJSXLike: true, dts: true }],
162+
'decorators-legacy'
163+
)
164+
}
165+
166+
forExtension(e: string): Parser {
167+
if (/(\.|^)([cm]?jsx?(\.flow)?)/.test(e)) return this.forJs
168+
if (/(\.|^)\.d\.ts/i.test(e)) return this.forDts
169+
if (/(\.|^)\.[cm]?tsx/i.test(e)) return this.forTsx
170+
if (/(\.|^)\.[cm]?ts/i.test(e)) return this.forTs
171+
return this
172+
}
70173
}
71174

72-
const tsPlugins: ParserPlugin[] = [
73-
'asyncGenerators',
74-
'bigInt',
75-
'classPrivateMethods',
76-
'classPrivateProperties',
77-
'classProperties',
78-
'decorators-legacy',
79-
'doExpressions',
80-
'dynamicImport',
81-
'exportDefaultFrom',
82-
'exportNamespaceFrom',
83-
'functionBind',
84-
'functionSent',
85-
'importMeta',
86-
'nullishCoalescingOperator',
87-
'numericSeparator',
88-
'objectRestSpread',
89-
'optionalCatchBinding',
90-
'optionalChaining',
91-
['pipelineOperator', { proposal: 'minimal' }],
92-
'throwExpressions',
93-
'typescript',
94-
]
95-
96-
export const tsParser: Parser = new Parser(defaultBabelParser, {
97-
sourceType: 'module',
98-
allowImportExportEverywhere: true,
99-
allowReturnOutsideFunction: true,
100-
startLine: 1,
101-
plugins: tsPlugins,
102-
})
103-
export const dtsParser: Parser = new Parser(defaultBabelParser, {
104-
sourceType: 'module',
105-
allowImportExportEverywhere: true,
106-
allowReturnOutsideFunction: true,
107-
startLine: 1,
108-
plugins: mergePlugins(tsPlugins, [['typescript', { dts: true }]]),
109-
})
110-
export const tsxParser: Parser = new Parser(defaultBabelParser, {
111-
...tsParser.parserOpts,
112-
plugins: [...tsPlugins, 'jsx'],
113-
})
114175
export const jsParser: Parser = new Parser(defaultBabelParser, {
115176
sourceType: 'module',
116177
allowImportExportEverywhere: true,
@@ -125,33 +186,29 @@ export const jsParser: Parser = new Parser(defaultBabelParser, {
125186
'classProperties',
126187
'classPrivateProperties',
127188
'classPrivateMethods',
128-
['decorators', { decoratorsBeforeExport: false }],
129-
'doExpressions',
189+
'classStaticBlock',
130190
'dynamicImport',
131-
'exportDefaultFrom',
132191
'exportNamespaceFrom',
133-
'functionBind',
134192
'functionSent',
135193
'importMeta',
136194
'logicalAssignment',
195+
'moduleStringNames',
137196
'nullishCoalescingOperator',
138197
'numericSeparator',
139198
'objectRestSpread',
140199
'optionalCatchBinding',
141200
'optionalChaining',
142-
['pipelineOperator', { proposal: 'minimal' }],
143-
'throwExpressions',
201+
'privateIn',
202+
'topLevelAwait',
144203
],
145204
})
146205

206+
export const tsParser: Parser = jsParser.forTs
207+
export const tsxParser: Parser = jsParser.forTsx
208+
export const dtsParser: Parser = jsParser.forDts
209+
147210
function defaultParser(extname: string): Parser {
148-
return extname === '.tsx'
149-
? tsxParser
150-
: extname === '.d.ts'
151-
? dtsParser
152-
: extname === '.ts'
153-
? tsParser
154-
: jsParser
211+
return jsParser.forExtension(extname)
155212
}
156213

157214
const resolve: (

test/index.spec.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ describe('parseSync', function () {
5656
it(`falls back to sensible defaults if babel not found`, async function () {
5757
const dir = os.tmpdir()
5858
const file = Path.join(dir, 'test.js')
59-
await fs.writeFile(file, `const foo = bar |> baz`, 'utf8')
59+
await fs.writeFile(file, `await foo`, 'utf8')
6060
expect(parseSync(file).type).to.equal('File')
6161
})
6262
it('works', () => {
@@ -163,7 +163,7 @@ describe(`parseAsync`, function () {
163163
it(`falls back to sensible defaults if babel not found`, async function () {
164164
const dir = os.tmpdir()
165165
const file = Path.join(dir, 'test.js')
166-
await fs.writeFile(file, `const foo = bar |> baz`, 'utf8')
166+
await fs.writeFile(file, `await foo`, 'utf8')
167167
expect((await parseAsync(file)).type).to.equal('File')
168168
})
169169
it('works', async () => {

0 commit comments

Comments
 (0)