Skip to content

Commit a19ce7b

Browse files
authored
fix: use destructure handler for v-for (#19)
1 parent b23889d commit a19ce7b

File tree

2 files changed

+41
-30
lines changed

2 files changed

+41
-30
lines changed

src/utils/template.ts

Lines changed: 36 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { AttributeNode, DirectiveNode, ExpressionNode, ParentNode, RootNode, SourceLocation, TemplateChildNode, TextNode } from '@vue/compiler-dom'
1+
import type { AttributeNode, DirectiveNode, ExpressionNode, ForParseResult, ParentNode, RootNode, SourceLocation, TemplateChildNode, TextNode } from '@vue/compiler-dom'
22

33
// copy from `@vue/compiler-dom`
44
enum NodeTypes {
@@ -40,6 +40,7 @@ enum NodeTypes {
4040
interface ExpressionTrack {
4141
type: NodeTypes
4242
name?: string
43+
forParseResult?: ForParseResult
4344
}
4445

4546
interface Expression {
@@ -163,17 +164,18 @@ export async function transpileVueTemplate(
163164
for (const item of expressions) {
164165
item.replacement = transformMap.get(item) ?? item.src
165166

166-
const surrounding = getSurrounding(
167+
// the source should only have one of the quotes
168+
const sourceQuote = getSourceQuote(
167169
content,
168170
item.loc.start.offset - offset,
169171
item.loc.end.offset - offset,
170172
)
171-
if (surrounding) {
172-
const replace = surrounding.code === `"` ? `'` : `"`
173+
if (sourceQuote !== null) {
174+
const search = sourceQuote === `"` ? `'` : `"`
173175
item.replacement = replaceQuote(
174176
item.replacement,
175-
surrounding.code,
176-
replace,
177+
search,
178+
sourceQuote,
177179
)
178180
}
179181
}
@@ -210,25 +212,15 @@ function replaceQuote(code: string, target: string, replace: string): string {
210212
return res
211213
}
212214

213-
function getSurrounding(code: string, start: number, end: number) {
214-
const empty = new Set<string | undefined>([' ', '\n', '\r', '\t'])
215-
let startIndex = start - 1
216-
let endIndex = end
217-
218-
while (startIndex > 0 && empty.has(code.at(startIndex))) {
219-
startIndex--
220-
}
221-
222-
while (endIndex < code.length && empty.has(code.at(endIndex))) {
223-
endIndex++
215+
function getSourceQuote(code: string, start: number, end: number): string | null {
216+
const source = code.slice(start, end)
217+
const quotes = ['"', '\'']
218+
for (const quote of quotes) {
219+
if (source.includes(quote)) {
220+
return quote
221+
}
224222
}
225-
226-
const prev = startIndex >= 0 ? code.at(startIndex) : ''
227-
const next = endIndex < code.length ? code.at(endIndex) : ''
228-
229-
return prev && next && prev === next
230-
? { code: prev, prevAt: startIndex, nextAt: endIndex }
231-
: undefined
223+
return null
232224
}
233225

234226
interface SnippetHandler {
@@ -254,11 +246,26 @@ const defaultSnippetHandler: SnippetHandler = {
254246
standalone: false,
255247
}
256248

257-
const vSlotSnippetHandler: SnippetHandler = {
249+
const destructureSnippetHandler: SnippetHandler = {
258250
key: (node) => {
251+
const key = `destructure$:${node.src}`
252+
const lastTrack = node.track.at(-1)
259253
const secondLastTrack = node.track.at(-2)
254+
255+
// v-slot:xxx="{ name }"
260256
if (secondLastTrack?.type === NodeTypes.DIRECTIVE && secondLastTrack.name === 'slot') {
261-
return `vSlot$:${node.src}`
257+
return key
258+
}
259+
260+
// v-for="({ name }, key, index) of items"
261+
// ^this ^this ^this ^not this
262+
if (
263+
secondLastTrack?.type === NodeTypes.DIRECTIVE
264+
&& secondLastTrack.name === 'for'
265+
&& secondLastTrack?.forParseResult
266+
&& lastTrack !== secondLastTrack.forParseResult.source
267+
) {
268+
return key
262269
}
263270
return null
264271
},
@@ -274,7 +281,7 @@ const vSlotSnippetHandler: SnippetHandler = {
274281
standalone: true,
275282
}
276283

277-
const snippetHandlers = [vSlotSnippetHandler, defaultSnippetHandler]
284+
const snippetHandlers = [destructureSnippetHandler, defaultSnippetHandler]
278285
function getKey(expression: Expression) {
279286
for (const handler of snippetHandlers) {
280287
const key = handler.key(expression)
@@ -339,7 +346,8 @@ async function transformJsSnippets(expressions: Expression[], transform: (code:
339346

340347
// transform standalone snippets
341348
await Promise.all(standalone.map(async ({ id, handler, nodes }) => {
342-
const line = await transform(handler.prepare(nodes[0], id))
349+
const prepared = handler.prepare(nodes[0], id)
350+
const line = await transform(prepared)
343351

344352
const res = handler.parse(line.trim(), id)
345353
if (!res) {

test/template.test.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,17 @@ import { transpileVueTemplate } from '../src/utils/template'
66

77
describe('transform typescript template', () => {
88
it('v-for', async () => {
9-
expect(await fixture(`<div v-for="item as string in items as unknown[]" :key="item">{{ item }}</div>`))
9+
expect(await fixture(`<div v-for="item in items as unknown[]" :key="item">{{ item }}</div>`))
1010
.toEqual(`<div v-for="item in items" :key="item">{{ item }}</div>`)
1111

12-
expect(await fixture(`<div v-for="(item as string, index) in items as unknown[]" :key="item" :index>{{ item }}</div>`))
12+
expect(await fixture(`<div v-for="(item, index) in items as unknown[]" :key="item" :index>{{ item }}</div>`))
1313
.toEqual(`<div v-for="(item, index) in items" :key="item" :index>{{ item }}</div>`)
1414

1515
expect(await fixture(`<div v-for="(item, index) of items" />`))
1616
.toEqual(`<div v-for="(item, index) of items" />`)
17+
18+
expect(await fixture(`<div v-for="({ name = 'Tony' }, index) of items" />`))
19+
.toEqual(`<div v-for="({ name = 'Tony' }, index) of items" />`)
1720
})
1821

1922
it('v-if', async () => {

0 commit comments

Comments
 (0)