Skip to content

Commit 4054e0a

Browse files
authored
Fix error when {#await} with complex expression. (#121)
* Fix error when `{#await}` with complex expression. * fix fixture
1 parent 9ced62b commit 4054e0a

File tree

7 files changed

+31872
-667
lines changed

7 files changed

+31872
-667
lines changed

src/context/script-let.ts

+151-24
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,8 @@ type RestoreCallback = {
9797
callback: ScriptLetRestoreCallback
9898
}
9999

100+
type TypeGenHelper = { generateUniqueId: (base: string) => string }
101+
100102
/**
101103
* A class that handles script fragments.
102104
* The script fragment AST node remaps and connects to the original directive AST node.
@@ -110,6 +112,10 @@ export class ScriptLetContext {
110112

111113
private readonly closeScopeCallbacks: (() => void)[] = []
112114

115+
private uniqueIdSeq = 1
116+
117+
private readonly usedUniqueIds = new Set<string>()
118+
113119
public constructor(ctx: Context) {
114120
this.script = ctx.sourceCode.scripts
115121
this.ctx = ctx
@@ -327,7 +333,12 @@ export class ScriptLetContext {
327333
nodes: ESTree.Pattern[],
328334
options: ScriptLetCallbackOption,
329335
) => void,
330-
typings: string[],
336+
typings:
337+
| string[]
338+
| ((helper: TypeGenHelper) => {
339+
typings: string[]
340+
preparationScript?: string
341+
}),
331342
): void
332343

333344
public nestBlock(
@@ -338,8 +349,34 @@ export class ScriptLetContext {
338349
nodes: ESTree.Pattern[],
339350
options: ScriptLetCallbackOption,
340351
) => void,
341-
typings?: string[],
352+
typings?:
353+
| string[]
354+
| ((helper: TypeGenHelper) => {
355+
typings: string[]
356+
preparationScript?: string
357+
}),
342358
): void {
359+
let arrayTypings: string[] = []
360+
if (typings && this.ctx.isTypeScript()) {
361+
if (Array.isArray(typings)) {
362+
arrayTypings = typings
363+
} else {
364+
const generatedTypes = typings({
365+
generateUniqueId: (base) => this.generateUniqueId(base),
366+
})
367+
arrayTypings = generatedTypes.typings
368+
if (generatedTypes.preparationScript) {
369+
this.appendScriptWithoutOffset(
370+
generatedTypes.preparationScript,
371+
(node, tokens, comments, result) => {
372+
tokens.length = 0
373+
comments.length = 0
374+
removeAllReference(node, result)
375+
},
376+
)
377+
}
378+
}
379+
}
343380
if (!params) {
344381
const restore = this.appendScript(
345382
`{`,
@@ -381,7 +418,7 @@ export class ScriptLetContext {
381418
range,
382419
})
383420
if (this.ctx.isTypeScript()) {
384-
source += ` : (${typings![index]})`
421+
source += ` : (${arrayTypings[index]})`
385422
}
386423
}
387424
const restore = this.appendScript(
@@ -407,6 +444,8 @@ export class ScriptLetContext {
407444
line: typeAnnotation.loc.start.line,
408445
column: typeAnnotation.loc.start.column,
409446
}
447+
448+
removeAllReference(typeAnnotation, result)
410449
}
411450
}
412451

@@ -462,21 +501,37 @@ export class ScriptLetContext {
462501
options: ScriptLetCallbackOption,
463502
) => void,
464503
) {
465-
const { start: startOffset, end: endOffset } = this.script.addLet(text)
466-
467-
const restoreCallback: RestoreCallback = {
468-
start: startOffset,
469-
end: endOffset,
470-
callback: (node, tokens, comments, result) => {
504+
const resultCallback = this.appendScriptWithoutOffset(
505+
text,
506+
(node, tokens, comments, result) => {
471507
this.fixLocations(
472508
node,
473509
tokens,
474510
comments,
475-
offset - startOffset,
511+
offset - resultCallback.start,
476512
result.visitorKeys,
477513
)
478514
callback(node, tokens, comments, result)
479515
},
516+
)
517+
return resultCallback
518+
}
519+
520+
private appendScriptWithoutOffset(
521+
text: string,
522+
callback: (
523+
node: ESTree.Node,
524+
tokens: Token[],
525+
comments: Comment[],
526+
options: ScriptLetCallbackOption,
527+
) => void,
528+
) {
529+
const { start: startOffset, end: endOffset } = this.script.addLet(text)
530+
531+
const restoreCallback: RestoreCallback = {
532+
start: startOffset,
533+
end: endOffset,
534+
callback,
480535
}
481536
this.restoreCallbacks.push(restoreCallback)
482537
return restoreCallback
@@ -746,6 +801,19 @@ export class ScriptLetContext {
746801
applyLocs(t, locs)
747802
}
748803
}
804+
805+
private generateUniqueId(base: string) {
806+
let candidate = `$_${base.replace(/\W/g, "_")}${this.uniqueIdSeq++}`
807+
while (
808+
this.usedUniqueIds.has(candidate) ||
809+
this.ctx.code.includes(candidate) ||
810+
this.script.vcode.includes(candidate)
811+
) {
812+
candidate = `$_${base.replace(/\W/g, "_")}${this.uniqueIdSeq++}`
813+
}
814+
this.usedUniqueIds.add(candidate)
815+
return candidate
816+
}
749817
}
750818

751819
/**
@@ -775,22 +843,15 @@ function getScope(scopeManager: ScopeManager, currentNode: ESTree.Node): Scope {
775843
function getInnermostScope(initialScope: Scope, node: ESTree.Node): Scope {
776844
const location = node.range![0]
777845

778-
let scope = initialScope
779-
let found = false
780-
do {
781-
found = false
782-
for (const childScope of scope.childScopes) {
783-
const range = childScope.block.range!
846+
for (const childScope of initialScope.childScopes) {
847+
const range = childScope.block.range!
784848

785-
if (range[0] <= location && location < range[1]) {
786-
scope = childScope
787-
found = true
788-
break
789-
}
849+
if (range[0] <= location && location < range[1]) {
850+
return getInnermostScope(childScope, node)
790851
}
791-
} while (found)
852+
}
792853

793-
return scope
854+
return initialScope
794855
}
795856

796857
/**
@@ -807,10 +868,76 @@ function applyLocs(target: Locations | ESTree.Node, locs: Locations) {
807868
}
808869
}
809870

871+
/** Remove all reference */
872+
function removeAllReference(
873+
target: ESTree.Node,
874+
result: ScriptLetCallbackOption,
875+
) {
876+
traverseNodes(target, {
877+
visitorKeys: result.visitorKeys,
878+
enterNode(node) {
879+
if (node.type === "Identifier") {
880+
const scope = result.getScope(node)
881+
882+
removeIdentifierReference(node, scope)
883+
}
884+
},
885+
leaveNode() {
886+
// noop
887+
},
888+
})
889+
}
890+
891+
/** Remove reference */
892+
function removeIdentifierReference(
893+
node: ESTree.Identifier,
894+
scope: Scope,
895+
): boolean {
896+
const reference = scope.references.find((ref) => ref.identifier === node)
897+
if (reference) {
898+
removeReference(reference, scope)
899+
return true
900+
}
901+
const location = node.range![0]
902+
903+
const pendingScopes = []
904+
for (const childScope of scope.childScopes) {
905+
const range = childScope.block.range!
906+
907+
if (range[0] <= location && location < range[1]) {
908+
if (removeIdentifierReference(node, childScope)) {
909+
return true
910+
}
911+
} else {
912+
pendingScopes.push(childScope)
913+
}
914+
}
915+
for (const childScope of pendingScopes) {
916+
if (removeIdentifierReference(node, childScope)) {
917+
return true
918+
}
919+
}
920+
return false
921+
}
922+
810923
/** Remove reference */
811924
function removeReference(reference: Reference, baseScope: Scope) {
812-
let scope: Scope | null = baseScope
925+
if (
926+
reference.resolved &&
927+
reference.resolved.defs.some((d) => d.name === reference.identifier)
928+
) {
929+
// remove var
930+
const varIndex = baseScope.variables.indexOf(reference.resolved)
931+
if (varIndex >= 0) {
932+
baseScope.variables.splice(varIndex, 1)
933+
}
934+
const name = reference.identifier.name
935+
if (reference.resolved === baseScope.set.get(name)) {
936+
baseScope.set.delete(name)
937+
}
938+
}
813939

940+
let scope: Scope | null = baseScope
814941
while (scope) {
815942
const refIndex = scope.references.indexOf(reference)
816943
if (refIndex >= 0) {

src/parser/converts/block.ts

+20-5
Original file line numberDiff line numberDiff line change
@@ -291,11 +291,26 @@ export function convertAwaitBlock(
291291
([value]) => {
292292
thenBlock.value = value
293293
},
294-
[
295-
`Parameters<Parameters<(typeof ${ctx.getText(
296-
node.expression,
297-
)})["then"]>[0]>[0]`,
298-
],
294+
({ generateUniqueId }) => {
295+
const expression = ctx.getText(node.expression)
296+
if (
297+
node.expression.type === "Identifier" ||
298+
node.expression.type === "Literal"
299+
) {
300+
return {
301+
typings: [
302+
`Parameters<Parameters<(typeof ${expression})["then"]>[0]>[0]`,
303+
],
304+
}
305+
}
306+
const id = generateUniqueId(expression)
307+
return {
308+
preparationScript: `const ${id} = ${expression};`,
309+
typings: [
310+
`Parameters<Parameters<(typeof ${id})["then"]>[0]>[0]`,
311+
],
312+
}
313+
},
299314
)
300315
} else {
301316
ctx.scriptLet.nestBlock(thenBlock)

0 commit comments

Comments
 (0)