// 找出「相邻兄弟元素带同名 slot」的写法。
|
//
|
// 为什么单独查这个:`.sync`/`slot` 那套 codemod 会把每个带 slot="x" 的元素单独包一层
|
// <template #x>。如果两个相邻兄弟带的是同一个 slot 名(典型是 v-if / v-else 两段
|
// footer),就会被拆成两个 <template #x>,既触发「v-else 没有相邻 v-if」,又触发
|
// 「同名插槽重复」。正确写法是把这一串兄弟合并进同一个 <template #x>。
|
//
|
// 用法:node scripts/migrate/find-dup-slot.mjs <dir...>
|
import { readFileSync, readdirSync, statSync } from 'node:fs'
|
import { join, extname } from 'node:path'
|
import { parse } from '@vue/compiler-sfc'
|
|
function collect(dir, out = []) {
|
for (const e of readdirSync(dir)) {
|
const p = join(dir, e)
|
if (statSync(p).isDirectory()) collect(p, out)
|
else if (extname(p) === '.vue') out.push(p)
|
}
|
return out
|
}
|
|
function slotNameOf(node) {
|
const a = (node.props || []).find(
|
p => (p.type === 6 && p.name === 'slot') || (p.type === 7 && p.name === 'slot' && p.arg)
|
)
|
if (!a) return null
|
return a.type === 6 ? a.value && a.value.content : a.arg.content
|
}
|
|
const targets = process.argv.slice(2)
|
const files = targets.flatMap(t => collect(t))
|
let sites = 0
|
const filesHit = new Set()
|
|
for (const f of files) {
|
const src = readFileSync(f, 'utf8')
|
let descriptor
|
try {
|
descriptor = parse(src, { filename: f }).descriptor
|
} catch {
|
continue
|
}
|
const ast = descriptor.template?.ast
|
if (!ast) continue
|
const visit = n => {
|
if (!n || typeof n !== 'object') return
|
if (n.type === 1) {
|
const kids = (n.children || []).filter(c => c.type === 1)
|
for (let i = 1; i < kids.length; i++) {
|
const a = slotNameOf(kids[i - 1])
|
const b = slotNameOf(kids[i])
|
if (a && b && a === b) {
|
sites++
|
filesHit.add(f)
|
console.log(` ${f} L${kids[i].loc.start.line} <${n.tag}> 相邻同名 slot="${a}"`)
|
break
|
}
|
}
|
}
|
for (const c of n.children || []) visit(c)
|
}
|
visit(ast)
|
}
|
|
console.log(`\n=== 相邻同名 slot:${sites} 处 / ${filesHit.size} 文件`)
|