























@@ -0,0 +1,131 @@
1+#!/usr/bin/env node
2+import fs from "node:fs";
3+import { createRequire } from "node:module";
4+import path from "node:path";
5+import { fileURLToPath } from "node:url";
6+7+const require = createRequire(import.meta.url);
8+const ts = require("typescript");
9+10+const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
11+const SCAN_ROOTS = ["src", "extensions", "packages"];
12+const SOURCE_FILE_RE = /\.(?:ts|tsx)$/;
13+const SKIP_PATH_RE =
14+/(?:^|\/)(?:node_modules|dist|build|protocol-generated)(?:\/|$)|(?:\.test|\.spec|\.e2e|\.generated)\.tsx?$/;
15+const DEPRECATED_SURFACE_COMMENT_RE =
16+/^(?:back-compat alias|backward-compatible alias(?:es)?|deprecated alias|legacy alias|legacy field|legacy:\s|kept for compatibility with existing imports|keep the legacy helper name exported)\b/i;
17+18+function walk(dir, files = []) {
19+if (!fs.existsSync(dir)) {
20+return files;
21+}
22+for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
23+const filePath = path.join(dir, entry.name);
24+const relativePath = path.relative(repoRoot, filePath).replaceAll(path.sep, "/");
25+if (SKIP_PATH_RE.test(relativePath)) {
26+continue;
27+}
28+if (entry.isDirectory()) {
29+walk(filePath, files);
30+} else if (SOURCE_FILE_RE.test(entry.name)) {
31+files.push(filePath);
32+}
33+}
34+return files;
35+}
36+37+function leadingCommentText(sourceFile, node) {
38+return (ts.getLeadingCommentRanges(sourceFile.text, node.pos) ?? [])
39+.map((range) => sourceFile.text.slice(range.pos, range.end))
40+.join("\n");
41+}
42+43+function normalizeCommentText(comment) {
44+return comment
45+.replace(/\/\*\*?/g, "")
46+.replace(/\*\//g, "")
47+.split("\n")
48+.map((line) => line.replace(/^\s*(?:\*|\/\/)\s?/, "").trim())
49+.filter(Boolean)
50+.join(" ");
51+}
52+53+function lineOf(sourceFile, node) {
54+return sourceFile.getLineAndCharacterOfPosition(node.getStart(sourceFile)).line + 1;
55+}
56+57+function isExported(node) {
58+return (
59+node.modifiers?.some((modifier) => modifier.kind === ts.SyntaxKind.ExportKeyword) ||
60+node.parent?.kind === ts.SyntaxKind.SourceFile
61+);
62+}
63+64+function symbolName(node) {
65+const declaration = node.declarationList?.declarations?.[0] ?? node;
66+return declaration.name?.getText?.() ?? "<anonymous>";
67+}
68+69+function shouldInspectNode(node) {
70+if (
71+ts.isFunctionDeclaration(node) ||
72+ts.isClassDeclaration(node) ||
73+ts.isInterfaceDeclaration(node) ||
74+ts.isTypeAliasDeclaration(node) ||
75+ts.isEnumDeclaration(node) ||
76+ts.isVariableStatement(node)
77+) {
78+return isExported(node);
79+}
80+return (
81+ts.isPropertySignature(node) ||
82+ts.isMethodSignature(node) ||
83+ts.isPropertyDeclaration(node) ||
84+ts.isEnumMember(node)
85+);
86+}
87+88+function collectViolations(filePath) {
89+const sourceText = fs.readFileSync(filePath, "utf8");
90+const sourceFile = ts.createSourceFile(filePath, sourceText, ts.ScriptTarget.Latest, true);
91+const violations = [];
92+93+function visit(node) {
94+if (shouldInspectNode(node)) {
95+const comment = leadingCommentText(sourceFile, node);
96+const normalizedComment = normalizeCommentText(comment);
97+if (
98+normalizedComment &&
99+DEPRECATED_SURFACE_COMMENT_RE.test(normalizedComment) &&
100+!/@deprecated\b/.test(comment)
101+) {
102+violations.push({
103+line: lineOf(sourceFile, node),
104+name: symbolName(node),
105+filePath: path.relative(repoRoot, filePath).replaceAll(path.sep, "/"),
106+});
107+}
108+}
109+ts.forEachChild(node, visit);
110+}
111+112+visit(sourceFile);
113+return violations;
114+}
115+116+const violations = SCAN_ROOTS.flatMap((root) =>
117+walk(path.join(repoRoot, root)).flatMap(collectViolations),
118+);
119+120+if (violations.length > 0) {
121+console.error("Deprecated JSDoc guard failed:");
122+for (const violation of violations) {
123+console.error(`- ${violation.filePath}:${violation.line} ${violation.name}`);
124+}
125+console.error(
126+"Add an @deprecated JSDoc tag or reword the comment if the symbol is not deprecated.",
127+);
128+process.exitCode = 1;
129+} else {
130+console.log("deprecated JSDoc guard passed");
131+}
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。