/

(含属性)这类"正常标签"
*/
import { promises as fs } from "node:fs";
import fg from "fast-glob";
+const files = await fg(["content/docs/**/*.md"], { dot: false });
+
/**
- * 极简策略:
- * 1) 跳过 fenced code / inline code(保留原样)
- * 2) 仅在普通文本行内转义形如 <数字开头...> 或 <单词里含逗号/空格/数学符号...> 的片段
- * 3) 不动像
/ 这类“正常标签/组件名”的片段
+ * 正常 HTML/JSX 标签匹配模式(用于 negative lookahead):
+ * - 可选 `/` 表示闭合标签 (
)
+ * - 标签名首字母为字母,后跟字母/数字/冒号/下划线/连字符
+ * - 可选属性段:空格后跟任意非尖括号字符([^<>] 防 ReDoS)
+ * - 可选 `/` 表示自闭合 (
)
+ * - `>` 收尾
+ *
+ * 接受样例(lookahead 命中,不 escape):
+ *
+ *
+ *
+ *
+ * 不接受样例(lookahead miss,进 escape 分支):
+ * <8> <1,2,3>
*/
-const files = await fg(["content/docs/**/*.md"], { dot: false });
+const VALID_TAG_LOOKAHEAD = /\/?[A-Za-z][A-Za-z0-9:_-]*([ \t][^<>]*)?\s*\/?>/;
for (const file of files) {
let src = await fs.readFile(file, "utf8");
@@ -30,14 +57,14 @@ for (const file of files) {
return `__CODE_BLOCK_${blocks.length - 1}__`;
});
- // 在普通文本里做“可疑尖括号”的转义:
+ // 在普通文本里做"可疑尖括号"的转义:
// - <\d...> 如 <8>、<1,2,3>
// - <[^\s/>][^>]*[,;+\-*/= ]+[^>]*> 含明显非标签符号的
src = src
.replace(/<\d[^>]*>/g, (m) =>
m.replaceAll("<", "<").replaceAll(">", ">"),
)
- .replace(/<(?![A-Za-z/][A-Za-z0-9:_-]*\s*\/?>)[^>]*>/g, (m) =>
+ .replace(new RegExp(`<(?!${VALID_TAG_LOOKAHEAD.source})[^>]*>`, "g"), (m) =>
m.replaceAll("<", "<").replaceAll(">", ">"),
);