Skip to content

Commit 4885687

Browse files
committed
feat: auto detect this usage
1 parent fac3376 commit 4885687

File tree

6 files changed

+124
-57
lines changed

6 files changed

+124
-57
lines changed

README.md

Lines changed: 14 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,16 @@ Here is an example:
110110

111111
```ts
112112
import fs from 'node:fs'
113-
import { quansyncMacro } from 'quansync'
113+
import { quansync } from 'quansync/macro'
114114

115115
// Create a quansync function by providing `sync` and `async` implementations
116-
const readFile = quansyncMacro({
116+
const readFile = quansync({
117117
sync: (path: string) => fs.readFileSync(path),
118118
async: (path: string) => fs.promises.readFile(path),
119119
})
120120

121121
// Create a quansync function by providing an **async** function
122-
const myFunction = quansyncMacro(async function (filename) {
122+
const myFunction = quansync(async (filename) => {
123123
// Use `await` to call another quansync function
124124
const code = await readFile(filename, 'utf8')
125125

@@ -137,23 +137,24 @@ For more details on usage, refer to [quansync's docs](https://github.com/antfu-c
137137

138138
## How it works
139139

140-
`unplugin-quansync` transforms your async functions into generator functions with `quansyncMacro`,
141-
and transforms `await` into `yield`.
140+
`unplugin-quansync` transforms your async functions into generator functions
141+
wrapped by `quansync` from `quansync/macro`,
142+
replacing `await` with `yield`.
142143

143-
The example above is transformed into:
144+
The example above becomes:
144145

145146
```ts
146147
import fs from 'node:fs'
147-
import { quansyncMacro } from 'quansync'
148+
import { quansync } from 'quansync/macro'
148149

149150
// No transformations needed for objects
150-
const readFile = quansyncMacro({
151+
const readFile = quansync({
151152
sync: (path: string) => fs.readFileSync(path),
152153
async: (path: string) => fs.promises.readFile(path),
153154
})
154155

155156
// `async function` is transformed into a generator function
156-
const myFunction = quansyncMacro(function* (filename) {
157+
const myFunction = quansync(function* (filename) {
157158
// `await` is transformed into `yield ...`
158159
const code = yield readFile(filename, 'utf8')
159160

@@ -169,24 +170,22 @@ Both arrow functions and generators have been available since ES2015,
169170
but a "generator arrow function" syntax does not exist
170171
[yet](https://github.com/tc39/proposal-generator-arrow-functions).
171172

172-
You can still use arrow functions with `quansyncMacro`,
173+
You can still use arrow functions and `this` with `quansync` macro,
173174
but they will be transformed into generator functions,
174175
retaining `this` binding and omitting the `arguments` object.
175176

176177
```ts
177-
const echoNewLine = quansyncMacro(() => 42)
178+
const fn = quansync(() => this)
178179

179180
// Transforms to:
180181

181-
const echoNewLine = quansyncMacro((v) => {
182+
const fn = quansync((v) => {
182183
return function* () {
183-
return 42
184+
return this
184185
}.call(this)
185186
})
186187
```
187188

188-
To minimize runtime overhead, prioritize using regular functions.
189-
190189
## Sponsors
191190

192191
<p align="center">

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@
7070
"prepublishOnly": "pnpm run build"
7171
},
7272
"peerDependencies": {
73-
"quansync": ">=0.2.0"
73+
"quansync": ">=0.2.2"
7474
},
7575
"dependencies": {
7676
"ast-kit": "^1.4.0",
@@ -88,7 +88,7 @@
8888
"eslint": "^9.21.0",
8989
"oxc-transform": "^0.52.0",
9090
"prettier": "^3.5.2",
91-
"quansync": "^0.2.0",
91+
"quansync": "^0.2.2",
9292
"tsdown": "^0.6.0",
9393
"tsx": "^4.19.3",
9494
"typescript": "^5.7.3",

pnpm-lock.yaml

Lines changed: 5 additions & 5 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/core/index.ts

Lines changed: 34 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
} from 'magic-string-ast'
1616
import type * as t from '@babel/types'
1717

18+
const THIS_REGEX = /\bthis\b/
1819
const ARROW_FN_START = `\nreturn function* () {`
1920
const ARROW_FN_END = `}.call(this)\n`
2021

@@ -90,27 +91,17 @@ export function transformQuansync(
9091
if (!inMacroFunction || !node.async) return
9192

9293
const name = 'id' in node && node.id ? node.id.name : ''
93-
const firstParam = node.params[0]
9494
const isArrowFunction = node.type === 'ArrowFunctionExpression'
9595

96-
if (isArrowFunction) {
97-
if (firstParam) {
98-
s.overwrite(node.start!, firstParam.start!, `(`)
99-
} else {
100-
s.overwrite(node.start!, node.body.start!, `() => `)
101-
}
96+
const body = s.slice(node.body.start!, node.body.end!)
97+
const hasParentThis = isArrowFunction && THIS_REGEX.test(body)
10298

103-
if (node.body.type === 'BlockStatement') {
104-
s.appendLeft(node.body.start! + 1, ARROW_FN_START)
105-
s.appendLeft(node.body.end! - 1, ARROW_FN_END)
106-
} else {
107-
s.appendLeft(node.body.start!, `{${ARROW_FN_START}\nreturn `)
108-
s.appendLeft(node.body.end!, `\n${ARROW_FN_END}}`)
109-
}
110-
} else if (firstParam) {
111-
s.overwrite(node.start!, firstParam.start!, `function* ${name}(`)
99+
if (hasParentThis) {
100+
rewriteFunctionSignature(node, '(', ') => ')
101+
rewriteFunctionBody(node, ARROW_FN_START, ARROW_FN_END)
112102
} else {
113-
s.overwrite(node.start!, node.body.start!, `function* ${name}()`)
103+
rewriteFunctionSignature(node, `function* ${name}(`, ') ')
104+
rewriteFunctionBody(node)
114105
}
115106
},
116107
leave(node) {
@@ -122,4 +113,30 @@ export function transformQuansync(
122113
})
123114

124115
return generateTransform(s, id)
116+
117+
function rewriteFunctionSignature(
118+
node: t.Function,
119+
start: string,
120+
end: string,
121+
) {
122+
const firstParam = node.params[0]
123+
if (firstParam) {
124+
s.overwrite(node.start!, firstParam.start!, start)
125+
s.overwrite(node.params.at(-1)!.end!, node.body.start!, end)
126+
} else {
127+
s.overwrite(node.start!, node.body.start!, start + end)
128+
}
129+
}
130+
131+
function rewriteFunctionBody(node: t.Function, prefix = '', suffix = '') {
132+
if (node.body.type === 'BlockStatement') {
133+
s.appendLeft(node.body.start! + 1, prefix)
134+
s.appendLeft(node.body.end! - 1, suffix)
135+
} else {
136+
// prepend `{[prefix]return ` in body
137+
s.appendLeft(node.body.start!, `{\n${prefix}return `)
138+
// append `[suffix]}` in
139+
s.appendLeft(node.body.end!, `${suffix}\n}`)
140+
}
141+
}
125142
}

tests/__snapshots__/transform.test.ts.snap

Lines changed: 41 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ export const getNumber = quansync({
3939
async: (id) => Promise.resolve(id),
4040
})
4141
42-
const inc1 = quansync(function* (){
42+
const inc1 = quansync(function* () {
4343
const value = yield getNumber(1)
4444
return value + 1
4545
})
@@ -49,7 +49,7 @@ const inc2 = quansync(function* (id) {
4949
return value + 1
5050
})
5151
52-
const inc3 = quansync(function* named(){
52+
const inc3 = quansync(function* named() {
5353
const value = yield getNumber(1)
5454
return value + 1
5555
})
@@ -59,22 +59,17 @@ const inc4 = quansync(function* named(id) {
5959
return value + 1
6060
})
6161
62-
const inc5 = quansync(() => {
63-
return function* () {
62+
const inc5 = quansync(function* () {
6463
const value = yield getNumber(1)
6564
return value + 1
66-
}.call(this)
6765
})
6866
69-
const inc6 = quansync((id) => {
70-
return function* () {
67+
const inc6 = quansync(function* (id) {
7168
const value = yield getNumber(1)
7269
return value + 1
73-
}.call(this)
7470
})
7571
76-
export const fn7 = quansync(() => {
77-
return function* () {
72+
export const fn7 = quansync(function* () {
7873
yield 1
7974
yield 2
8075
const fn = async () => {
@@ -91,18 +86,15 @@ return function* () {
9186
await 10
9287
}
9388
}
94-
}.call(this)
9589
})
9690
97-
quansync(() => {
98-
return function* () {
91+
quansync(function* () {
9992
;!(yield 1)
10093
;'' + (yield 1)
10194
;false && (yield 1)
10295
fn7()
10396
;(yield 1) < (yield 10)
10497
const x = (yield 1) < (yield 10)
105-
}.call(this)
10698
})
10799
108100
export default async () => {
@@ -116,6 +108,40 @@ export default async () => {
116108
"
117109
`;
118110
111+
exports[`transform > ./fixtures/bind-this.js 1`] = `
112+
"// @ts-check
113+
import { quansync } from 'quansync/macro'
114+
import { expect } from 'vitest'
115+
116+
const globalThis = this
117+
118+
const arrowFn = quansync(() => {
119+
return function* () {
120+
return this
121+
}.call(this)
122+
})
123+
124+
const normalFunction = quansync(function* () {
125+
return this
126+
})
127+
128+
class Cls {
129+
arrowFn() {
130+
return arrowFn.call(this)
131+
}
132+
normalFunction() {
133+
return normalFunction.call(this)
134+
}
135+
}
136+
137+
export default async () => {
138+
const cls = new Cls()
139+
expect(await cls.arrowFn()).toBe(globalThis)
140+
expect(await cls.normalFunction()).instanceOf(Cls)
141+
}
142+
"
143+
`;
144+
119145
exports[`transform > ./fixtures/inlined-arrow.js 1`] = `
120146
"// @ts-check
121147
import { quansync } from 'quansync/macro'
@@ -127,11 +153,8 @@ export const echo = quansync({
127153
})
128154
129155
const echoNewLine = quansync(
130-
/** @param {string|Promise<string>} v */ (v) =>
131-
{
132-
return function* () {
156+
/** @param {string|Promise<string>} v */ function* (v) {
133157
return ((yield echo(yield v))) + '\\n'
134-
}.call(this)
135158
},
136159
)
137160

tests/fixtures/bind-this.js

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// @ts-check
2+
import { quansync } from 'quansync/macro'
3+
import { expect } from 'vitest'
4+
5+
const globalThis = this
6+
7+
const arrowFn = quansync(async () => {
8+
return this
9+
})
10+
11+
const normalFunction = quansync(async function () {
12+
return this
13+
})
14+
15+
class Cls {
16+
arrowFn() {
17+
return arrowFn.call(this)
18+
}
19+
normalFunction() {
20+
return normalFunction.call(this)
21+
}
22+
}
23+
24+
export default async () => {
25+
const cls = new Cls()
26+
expect(await cls.arrowFn()).toBe(globalThis)
27+
expect(await cls.normalFunction()).instanceOf(Cls)
28+
}

0 commit comments

Comments
 (0)