Skip to content

Commit

Permalink
feat: sync. (#2)
Browse files Browse the repository at this point in the history
  • Loading branch information
knightedcodemonkey authored Jun 1, 2024
1 parent 7d391d6 commit cd48afa
Show file tree
Hide file tree
Showing 7 changed files with 67 additions and 28 deletions.
22 changes: 9 additions & 13 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ Provides a workaround for [swc/1366](https://github.com/swc-project/swc/issues/1

## Example

There are four exports `reparse`, `reparseFile`, `reparseSync` and `reparseFileSync`.

```js
import assert from 'node:assert/strict'
import { reparse } from '@knighted/reparse'
import assert from 'node:assert/strict'

const ast1 = await reparse('const foo = "bar"')
const ast2 = await reparse('const foo = "bar"')
Expand All @@ -25,19 +27,13 @@ assert.equal(ast1.span.start, ast2.span.start)
assert.equal(ast1.span.end, ast2.span.end)
```

You can also pass a file to `reparseFile`.

## Notes

This package makes use of [`child_process.fork()`](https://nodejs.org/api/child_process.html#child_processforkmodulepath-args-options) for each invokation of `reparse()`. Thus it makes use of a promise to resolve the response from the forked child process which does the parsing, so the functions provided from `@knighted/reparse` are both async.

If you are consuming this package from CommonJS you need to wrap calls to `reparse` and `reparseFile` in an `async` function since top level await is not available.
Sync file example:

```js
const { reparse } = require('@knighted/reparse')
const doReparse = async () => {
const ast = await reparse('const foo = "bar"')
}
import { reparseFileSync } from '@knighted/reparse'

const ast0 = reparseFileSync('./file.ts')
const ast1 = reparseFileSync('./file.ts')

doReparse()
console.log(ast0.span.start === ast1.span.start) // true
```
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@knighted/reparse",
"version": "1.0.0",
"version": "1.1.0",
"description": "Multiple swc parsings of the same file with correct spans.",
"type": "module",
"main": "dist/reparse.js",
Expand Down
2 changes: 1 addition & 1 deletion postbuild.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@ const dist = resolve(import.meta.dirname, 'dist')
const reparseCjs = join(dist, 'cjs', 'reparse.cjs')
const cjs = (await readFile(reparseCjs)).toString()

await writeFile(reparseCjs, cjs.replace(/child\.js/, 'child.cjs'))
await writeFile(reparseCjs, cjs.replace(/child\.js/, 'child.cjs').replace(/childSync\.js/, 'childSync.cjs'))
26 changes: 26 additions & 0 deletions src/childSync.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { argv, stdout } from 'node:process'

import { parseSync, parseFileSync } from '@swc/core'

const baseConfig = {
comments: false,
target: 'esnext',
isModule: true,
}
const tsConfig = {
syntax: 'typescript',
tsx: true,
}
const esConfig = {
syntax: 'ecmascript',
jsx: true,
importAttributes: true,
}
const parse = () => {
const parseOptions = Object.assign(baseConfig, argv[3] === 'es' ? esConfig : tsConfig)
const ast = argv[4] === 'file' ? parseFileSync(argv[2], parseOptions) : parseSync(argv[2], parseOptions)

stdout.write(JSON.stringify(ast))
}

parse()
21 changes: 13 additions & 8 deletions src/reparse.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fork } from 'node:child_process'
import { fork, spawnSync } from 'node:child_process'
import { join } from 'node:path'

import type { Module } from '@swc/core'
Expand All @@ -12,14 +12,7 @@ const isModule = (msg: unknown): msg is Module => {

return false
}

const forkChild = (...args: string[]) => {
/**
* Circumvent SWC double parse span bug where start/end line numbers
* are wrong on subsequent parsings.
*
* @see https://github.com/swc-project/swc/issues/1366
*/
const child = fork(join(import.meta.dirname, 'child.js'), [...args], {
serialization: 'advanced',
})
Expand All @@ -37,9 +30,21 @@ const forkChild = (...args: string[]) => {
})
})
}
const spawnChild = (...args: string[]) => {
const { stdout } = spawnSync('node', [join(import.meta.dirname, 'childSync.js'), ...args])
const ast = JSON.parse(stdout.toString()) as Module

return ast
}
export const reparse = (source: string, lang: Lang = 'ts') => {
return forkChild(source, lang)
}
export const reparseFile = (file: string, lang: Lang = 'ts') => {
return forkChild(file, lang, 'file')
}
export const reparseSync = (source: string, lang: Lang = 'ts') => {
return spawnChild(source, lang)
}
export const reparseFileSync = (file: string, lang: Lang = 'ts') => {
return spawnChild(file, lang, 'file')
}
14 changes: 13 additions & 1 deletion test/reparse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { describe, it } from 'node:test'
import assert from 'node:assert/strict'
import { resolve, join } from 'node:path'

import { reparse, reparseFile } from '../src/reparse.js'
import { reparse, reparseFile, reparseSync, reparseFileSync } from '../src/reparse.js'

const fixtures = resolve(import.meta.dirname, 'fixtures')

Expand All @@ -18,4 +18,16 @@ describe('@knighted/reparse', () => {
assert.equal(ast3.span.start, ast4.span.start)
assert.equal(ast3.span.end, ast4.span.end)
})

it('returns correct spans with multiple swc parsings synchronously', () => {
const ast1 = reparseSync('const foo = "bar"')
const ast2 = reparseSync('const foo = "bar"')
const ast3 = reparseFileSync(join(fixtures, 'file.ts'))
const ast4 = reparseFileSync(join(fixtures, 'file.ts'))

assert.equal(ast1.span.start, ast2.span.start)
assert.equal(ast1.span.end, ast2.span.end)
assert.equal(ast3.span.start, ast4.span.start)
assert.equal(ast3.span.end, ast4.span.end)
})
})

0 comments on commit cd48afa

Please sign in to comment.