gaoluyang
5 天以前 c7566bba45e6f90594d36ec4b80ac4ff89f577ee
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
// 扫描 el-radio / el-radio-button / el-checkbox / el-checkbox-button 上的 label 属性。
//
// element-plus 2.13:`label` 仍可作为「值」使用(actualValue 会回退到 label),
// 但同时 `label` 又是默认插槽的兜底文案(`renderSlot(..., () => label)`)。
// 所以:
//   - 有子节点(插槽)的元素,把 label 改名 value 是安全的(文案仍来自插槽)
//   - 没有子节点的元素,label 同时承担文案与值,改名会让文案变空 → 必须人工
//   - 已经有 value 的元素,label 只是文案,无 deprecation 告警,不用动
// 用法:node scripts/migrate/scan-radio-label.mjs <dir...>
import { readFileSync, readdirSync, statSync } from 'node:fs'
import { join, extname } from 'node:path'
import { parse } from '@vue/compiler-sfc'
 
const FAMILY = new Set(['el-radio', 'el-radio-button', 'el-checkbox', 'el-checkbox-button'])
 
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
}
 
/** 元素上的 label 属性(兼容 `label="x"` 与 `:label="x"`) */
function labelAttr(node) {
  return (node.props || []).find(p =>
    (p.type === 6 && p.name === 'label') ||
    (p.type === 7 && p.name === 'bind' && p.arg && p.arg.content === 'label')
  )
}
function hasValue(node) {
  return (node.props || []).some(p =>
    (p.type === 6 && p.name === 'value') ||
    (p.type === 7 && p.name === 'bind' && p.arg && p.arg.content === 'value')
  )
}
 
const targets = process.argv.slice(2)
const files = targets.flatMap(t => (statSync(t).isDirectory() ? collect(t) : [t]))
let ok = 0
let manual = 0
let skip = 0
 
for (const f of files) {
  let descriptor
  try {
    descriptor = parse(readFileSync(f, 'utf8'), { 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 && FAMILY.has(n.tag)) {
      const attr = labelAttr(n)
      if (attr) {
        const where = `<${n.tag}> L${n.loc.start.line}`
        if (hasValue(n)) {
          skip++
        } else if ((n.children || []).some(c => c.type !== 2 || c.content.trim())) {
          ok++
          if (process.env.VERBOSE) console.log(`  可改  ${f} ${where}`)
        } else {
          manual++
          console.log(`  人工  ${f} ${where}   ← 无子节点,label 兼作文案`)
        }
      }
    }
    for (const c of n.children || []) visit(c)
  }
  visit(ast)
}
 
console.log(`\n=== 有子节点、无 value(可机械改 label→value):${ok} 处`)
console.log(`=== 无子节点(需人工):${manual} 处`)
console.log(`=== 已有 value(label 仅作文案,不动):${skip} 处`)