Commit 2527dd12 authored by shrabbit's avatar shrabbit
Browse files

fix: 修复代码块注释行 HTML 标签泄漏为可见文本

原实现串联 4 个正则依次替换,注释替换生成的 span class 属性值 cb-comment 会被字符串正则再次匹配并嵌套包裹,导致原 span 开头被截断,浏览器无法解析就泄漏成可见文本如 class=class=cb-string>cb-comment>。

改写为 tokenizer 一次扫描:用 sticky 正则 y flag + lastIndex 控制位置,每个字符只被消费一次,匹配成功就跳过整段,彻底避免在已生成 HTML 上重复匹配。同时升级字符串正则支持转义字符。
parent 04ea09b6
Loading
Loading
Loading
Loading
+37 −22
Original line number Diff line number Diff line
@@ -49,35 +49,50 @@ const copy = async () => {
}

/**
 * 极简高亮:仅做关键字、字符串、注释、数字的简单着色。
 * 不引入完整 parser,避免增加包体积与复杂度。
 * 极简高亮:tokenizer 一次扫描完成,避免后续正则在已生成 HTML 上重复匹配
 * 导致标签被截断、属性值泄漏为可见文本(如 class="cb-string">"cb-comment">)。
 *
 * 每个字符只被消费一次:匹配成功就用 span 包裹并跳过整段,未匹配则原样输出。
 */
const highlight = (line: string) => {
  // 先转义 HTML
  let s = line
  const s = line
    .replace(/&/g, '&')
    .replace(/</g, '&lt;')
    .replace(/>/g, '&gt;')

  // 注释(行内)
  s = s.replace(/(\/\/.*)$/g, '<span class="cb-comment">$1</span>')

  // 字符串
  s = s.replace(
    /("[^"]*"|'[^']*'|`[^`]*`)/g,
    '<span class="cb-string">$1</span>'
  )

  // 关键字
  s = s.replace(
    /\b(var|let|const|function|return|if|else|for|foreach|new|public|private|static|partial|class|void|string|int|bool|async|await|true|false|null)\b/g,
    '<span class="cb-keyword">$1</span>'
  )

  // 数字
  s = s.replace(/\b(\d+)\b/g, '<span class="cb-number">$1</span>')

  return s
  // 按优先级匹配:注释 > 字符串 > 关键字 > 数字
  // 使用 sticky 正则 (y flag) + lastIndex 控制扫描位置
  const patterns: Array<{ re: RegExp; cls: string }> = [
    { re: /\/\/.*$/y, cls: 'cb-comment' },
    { re: /"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|`(?:[^`\\]|\\.)*`/y, cls: 'cb-string' },
    {
      re: /\b(var|let|const|function|return|if|else|for|foreach|new|public|private|static|partial|class|void|string|int|bool|async|await|true|false|null)\b/y,
      cls: 'cb-keyword'
    },
    { re: /\b\d+\b/y, cls: 'cb-number' }
  ]

  let result = ''
  let i = 0
  while (i < s.length) {
    let matched = false
    for (const p of patterns) {
      p.re.lastIndex = i
      const m = p.re.exec(s)
      if (m && m.index === i) {
        result += `<span class="${p.cls}">${m[0]}</span>`
        i += m[0].length
        matched = true
        break
      }
    }
    if (!matched) {
      result += s[i]
      i++
    }
  }
  return result
}
</script>