Skip to content

Commit 797aa0f

Browse files
committed
fix: several issues, finish test coverage
1 parent 7242731 commit 797aa0f

File tree

3 files changed

+210
-94
lines changed

3 files changed

+210
-94
lines changed

src/index.ts

Lines changed: 91 additions & 87 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import * as Path from 'path'
55
import _resolve from 'resolve'
66
import { promisify } from 'util'
77
import * as defaultBabelParser from '@babel/parser'
8-
import { ParserOptions } from '@babel/parser'
8+
import { ParserOptions, ParserPlugin } from '@babel/parser'
99
import { readFile as _readFile, readFileSync } from 'fs'
1010
const readFile = promisify(_readFile)
1111

@@ -42,39 +42,40 @@ export class Parser {
4242
}
4343
}
4444

45+
const tsPlugins: ParserPlugin[] = [
46+
'asyncGenerators',
47+
'bigInt',
48+
'classPrivateMethods',
49+
'classPrivateProperties',
50+
'classProperties',
51+
'decorators-legacy',
52+
'doExpressions',
53+
'dynamicImport',
54+
'exportDefaultFrom',
55+
'exportNamespaceFrom',
56+
'functionBind',
57+
'functionSent',
58+
'importMeta',
59+
'nullishCoalescingOperator',
60+
'numericSeparator',
61+
'objectRestSpread',
62+
'optionalCatchBinding',
63+
'optionalChaining',
64+
['pipelineOperator', { proposal: 'minimal' }],
65+
'throwExpressions',
66+
'typescript',
67+
]
68+
4569
const tsParser: Parser = new Parser(defaultBabelParser, {
4670
sourceType: 'module',
4771
allowImportExportEverywhere: true,
4872
allowReturnOutsideFunction: true,
4973
startLine: 1,
50-
tokens: true,
51-
plugins: [
52-
'asyncGenerators',
53-
'bigInt',
54-
'classPrivateMethods',
55-
'classPrivateProperties',
56-
'classProperties',
57-
'decorators-legacy',
58-
'doExpressions',
59-
'dynamicImport',
60-
'exportDefaultFrom',
61-
'exportNamespaceFrom',
62-
'functionBind',
63-
'functionSent',
64-
'importMeta',
65-
'nullishCoalescingOperator',
66-
'numericSeparator',
67-
'objectRestSpread',
68-
'optionalCatchBinding',
69-
'optionalChaining',
70-
['pipelineOperator', { proposal: 'minimal' }],
71-
'throwExpressions',
72-
'typescript',
73-
],
74+
plugins: tsPlugins,
7475
})
7576
const tsxParser: Parser = new Parser(defaultBabelParser, {
7677
...tsParser.parserOpts,
77-
plugins: [...(tsParser.parserOpts.plugins || []), 'jsx'],
78+
plugins: [...tsPlugins, 'jsx'],
7879
})
7980
const jsParser: Parser = new Parser(defaultBabelParser, {
8081
sourceType: 'module',
@@ -143,83 +144,86 @@ export function getParserSync(
143144
file: string,
144145
options?: Omit<ParserOptions, 'plugins'>
145146
): Parser {
146-
if (/\.ts$/.test(file)) return tsParser
147-
if (/\.tsx$/.test(file)) return tsxParser
148-
149-
const parentDir = Path.dirname(file)
150-
151-
let result = syncCache.get(parentDir)
152-
153-
if (!result) {
154-
try {
155-
const babelPath = _resolve.sync('@babel/core', {
156-
basedir: parentDir,
157-
})
158-
// eslint-disable-next-line @typescript-eslint/no-var-requires
159-
const babel = require(babelPath)
160-
requiredPaths.push(babelPath)
161-
162-
const parserPath = _resolve.sync('@babel/parser', {
163-
basedir: parentDir,
164-
})
165-
// eslint-disable-next-line @typescript-eslint/no-var-requires
166-
const parser = require(parserPath)
167-
requiredPaths.push(parserPath)
168-
169-
const config = babel.loadOptionsSync({
170-
filename: file,
171-
cwd: Path.dirname(file),
172-
rootMode: 'upward-optional',
173-
})
174-
result = createParserFromConfig(parser, config)
175-
} catch (error) {
176-
result = jsParser
177-
}
178-
syncCache.set(parentDir, result)
179-
asyncCache.set(parentDir, Promise.resolve(result))
180-
}
181-
return !options || isEmpty(options) ? result : result.bindParserOpts(options)
182-
}
183-
184-
export async function getParserAsync(
185-
file: string,
186-
options?: Omit<ParserOptions, 'plugins'>
187-
): Promise<Parser> {
188-
if (/\.ts$/.test(file)) return tsParser
189-
if (/\.tsx$/.test(file)) return tsxParser
190-
191-
const parentDir = Path.dirname(file)
192-
193-
let promise = asyncCache.get(parentDir)
194-
195-
if (!promise) {
196-
promise = (async (): Promise<Parser> => {
197-
let result
147+
let result
148+
if (/\.ts$/.test(file)) result = tsParser
149+
else if (/\.tsx$/.test(file)) result = tsxParser
150+
else {
151+
const parentDir = Path.dirname(file)
152+
result = syncCache.get(parentDir)
153+
154+
if (!result) {
198155
try {
199-
const babelPath = await resolve('@babel/core', {
156+
const babelPath = _resolve.sync('@babel/core', {
200157
basedir: parentDir,
201158
})
202-
const babel = await import(babelPath)
159+
// eslint-disable-next-line @typescript-eslint/no-var-requires
160+
const babel = require(babelPath)
203161
requiredPaths.push(babelPath)
204162

205-
const parserPath = await resolve('@babel/parser', {
163+
const parserPath = _resolve.sync('@babel/parser', {
206164
basedir: parentDir,
207165
})
208-
const parser = await import(parserPath)
166+
// eslint-disable-next-line @typescript-eslint/no-var-requires
167+
const parser = require(parserPath)
209168
requiredPaths.push(parserPath)
210169

211-
const config = await babel.loadOptionsAsync({
170+
const config = babel.loadOptionsSync({
212171
filename: file,
213-
cwd: process.cwd(),
172+
cwd: Path.dirname(file),
173+
rootMode: 'upward-optional',
214174
})
215175
result = createParserFromConfig(parser, config)
216176
} catch (error) {
217177
result = jsParser
218178
}
219179
syncCache.set(parentDir, result)
220-
return result
221-
})()
222-
asyncCache.set(parentDir, promise)
180+
asyncCache.set(parentDir, Promise.resolve(result))
181+
}
182+
}
183+
return !options || isEmpty(options) ? result : result.bindParserOpts(options)
184+
}
185+
186+
export async function getParserAsync(
187+
file: string,
188+
options?: Omit<ParserOptions, 'plugins'>
189+
): Promise<Parser> {
190+
let promise
191+
if (/\.ts$/.test(file)) promise = Promise.resolve(tsParser)
192+
else if (/\.tsx$/.test(file)) promise = Promise.resolve(tsxParser)
193+
else {
194+
const parentDir = Path.dirname(file)
195+
196+
promise = asyncCache.get(parentDir)
197+
198+
if (!promise) {
199+
promise = (async (): Promise<Parser> => {
200+
let result
201+
try {
202+
const babelPath = await resolve('@babel/core', {
203+
basedir: parentDir,
204+
})
205+
const babel = await import(babelPath)
206+
requiredPaths.push(babelPath)
207+
208+
const parserPath = await resolve('@babel/parser', {
209+
basedir: parentDir,
210+
})
211+
const parser = await import(parserPath)
212+
requiredPaths.push(parserPath)
213+
214+
const config = await babel.loadOptionsAsync({
215+
filename: file,
216+
cwd: parentDir,
217+
})
218+
result = createParserFromConfig(parser, config)
219+
} catch (error) {
220+
result = jsParser
221+
}
222+
syncCache.set(parentDir, result)
223+
return result
224+
})()
225+
asyncCache.set(parentDir, promise)
226+
}
223227
}
224228
const result = await promise
225229
return !options || isEmpty(options) ? result : result.bindParserOpts(options)
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import * as React from 'react'
2+
3+
type Foo = keyof { a: 1; b: 2 }
4+
5+
const bar = <Baz />

test/index.spec.ts

Lines changed: 114 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
/* eslint-env mocha */
22

33
import * as Path from 'path'
4-
import { parseSync, parseAsync } from '../src'
4+
import { parseSync, parseAsync, getParserSync, clearCache } from '../src'
5+
import fs from 'fs-extra'
56
import { spawn } from 'promisify-child-process'
7+
import { expect } from 'chai'
8+
import os from 'os'
69

710
const fixturesDir = Path.resolve(__dirname, 'fixtures')
811

@@ -17,20 +20,124 @@ before(async function () {
1720
describe('parseSync', function () {
1821
this.timeout(10000)
1922

20-
it('works', async () => {
21-
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.js'))
23+
beforeEach(() => {
24+
clearCache()
2225
})
23-
it('works on ts file', async () => {
24-
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.ts'))
26+
27+
it(`falls back to sensible defaults if babel not found`, async function () {
28+
const dir = os.tmpdir()
29+
const file = Path.join(dir, 'test.js')
30+
await fs.writeFile(file, `const foo = bar |> baz`, 'utf8')
31+
expect(parseSync(file).type).to.equal('File')
32+
})
33+
it('works', () => {
34+
expect(
35+
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.js')).type
36+
).to.equal('File')
37+
})
38+
it('works on ts file', () => {
39+
expect(
40+
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.ts')).type
41+
).to.equal('File')
42+
})
43+
it('works on tsx file', () => {
44+
expect(
45+
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.tsx')).type
46+
).to.equal('File')
47+
})
48+
it('passing options works for js file', function () {
49+
expect(parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.js')).tokens)
50+
.not.to.exist
51+
expect(
52+
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.js'), {
53+
tokens: true,
54+
}).tokens
55+
).to.exist
56+
})
57+
it('passing options works for ts file', function () {
58+
expect(parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.ts')).tokens)
59+
.not.to.exist
60+
expect(
61+
parseSync(Path.join(fixturesDir, 'babelPipeline', 'test.ts'), {
62+
tokens: true,
63+
}).tokens
64+
).to.exist
2565
})
2666
})
2767
describe(`parseAsync`, function () {
2868
this.timeout(10000)
2969

70+
it(`falls back to sensible defaults if babel not found`, async function () {
71+
const dir = os.tmpdir()
72+
const file = Path.join(dir, 'test.js')
73+
await fs.writeFile(file, `const foo = bar |> baz`, 'utf8')
74+
expect((await parseAsync(file)).type).to.equal('File')
75+
})
3076
it('works', async () => {
31-
await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.js'))
77+
expect(
78+
(await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.js')))
79+
.type
80+
).to.equal('File')
3281
})
3382
it('works on ts file', async () => {
34-
await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.ts'))
83+
expect(
84+
(await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.ts')))
85+
.type
86+
).to.equal('File')
87+
})
88+
it('works on tsx file', async () => {
89+
expect(
90+
(await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.tsx')))
91+
.type
92+
).to.equal('File')
93+
})
94+
it('passing options works for js file', async () => {
95+
expect(
96+
(await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.js')))
97+
.tokens
98+
).not.to.exist
99+
expect(
100+
(
101+
await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.js'), {
102+
tokens: true,
103+
})
104+
).tokens
105+
).to.exist
106+
})
107+
it('passing options works for ts file', async () => {
108+
expect(
109+
(await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.ts')))
110+
.tokens
111+
).not.to.exist
112+
expect(
113+
(
114+
await parseAsync(Path.join(fixturesDir, 'babelPipeline', 'test.ts'), {
115+
tokens: true,
116+
})
117+
).tokens
118+
).to.exist
119+
})
120+
})
121+
122+
describe(`Parser`, function () {
123+
describe(`.parse`, function () {
124+
it(`passing options works`, async function () {
125+
const file = Path.join(fixturesDir, 'babelPipeline', 'test.ts')
126+
const parser = getParserSync(file)
127+
expect(parser.parse(await fs.readFile(file, 'utf8')).tokens).not.to.exist
128+
expect(
129+
parser.parse(await fs.readFile(file, 'utf8'), { tokens: true }).tokens
130+
).to.exist
131+
})
132+
})
133+
describe(`.parseExpression`, function () {
134+
it(`passing options works`, async function () {
135+
const file = Path.join(fixturesDir, 'babelPipeline', 'test.ts')
136+
const parser = getParserSync(file)
137+
expect((parser.parseExpression('foo(bar)') as any).tokens).not.to.exist
138+
expect(
139+
(parser.parseExpression('foo(bar)', { tokens: true }) as any).tokens
140+
).to.exist
141+
})
35142
})
36143
})

0 commit comments

Comments
 (0)