feat(arch-scan): add @public JSDoc tag exemption mechanism for Server Actions
This commit is contained in:
Binary file not shown.
@@ -103,4 +103,17 @@ describe("queryViolations", () => {
|
||||
);
|
||||
expect(missing).toBeDefined();
|
||||
});
|
||||
|
||||
it("should exclude @public Server Actions from violations", () => {
|
||||
const db = setupTestDb();
|
||||
// 插入一个带 @public 标记的 Server Action(豁免权限校验)
|
||||
db.prepare(
|
||||
"INSERT INTO symbols (file_id, name, kind, is_exported, is_async, is_server_action, is_public, start_line, end_line) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)"
|
||||
).run(1, "publicLoginAction", "function", 1, 1, 1, 1, 60, 100);
|
||||
const violations = queryViolations(db);
|
||||
const missing = violations.server_actions_without_permission.find(
|
||||
(v) => v.name === "publicLoginAction"
|
||||
);
|
||||
expect(missing).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -141,6 +141,7 @@ export function queryViolations(db: Database.Database): Violations {
|
||||
`SELECT s.name, f.path FROM symbols s
|
||||
JOIN files f ON s.file_id = f.id
|
||||
WHERE s.is_server_action = 1
|
||||
AND s.is_public = 0
|
||||
AND NOT EXISTS (
|
||||
SELECT 1 FROM calls c
|
||||
JOIN symbols cs ON c.callee_id = cs.id
|
||||
|
||||
@@ -185,9 +185,24 @@ export function scanSymbols(db: Database.Database): void {
|
||||
.prepare("SELECT id, path FROM files")
|
||||
.all() as { id: number; path: string }[];
|
||||
const insertSymbol = db.prepare(
|
||||
`INSERT OR IGNORE INTO symbols (file_id, name, kind, is_exported, is_async, is_server_action, signature, start_line, end_line)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
`INSERT OR IGNORE INTO symbols (file_id, name, kind, is_exported, is_async, is_server_action, is_public, signature, start_line, end_line)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
||||
);
|
||||
const hasPublicTag = (node: Node): boolean => {
|
||||
try {
|
||||
// ts-morph JSDoc 仅在 JSDocableNode 上可用
|
||||
const anyNode = node as unknown as {
|
||||
getJsDocs?: () => Array<{ getTags: () => Array<{ getTagName: () => string }> }>;
|
||||
};
|
||||
if (typeof anyNode.getJsDocs !== "function") return false;
|
||||
const docs = anyNode.getJsDocs();
|
||||
return docs.some((doc) =>
|
||||
doc.getTags().some((tag) => tag.getTagName() === "public")
|
||||
);
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
for (const file of files) {
|
||||
let sourceFile;
|
||||
try {
|
||||
@@ -223,6 +238,7 @@ export function scanSymbols(db: Database.Database): void {
|
||||
const startLine = node.getStartLineNumber();
|
||||
const endLine = node.getEndLineNumber();
|
||||
const isServerAction = isServerFile && exported && async;
|
||||
const isPublic = hasPublicTag(node) ? 1 : 0;
|
||||
insertSymbol.run(
|
||||
file.id,
|
||||
name,
|
||||
@@ -230,6 +246,7 @@ export function scanSymbols(db: Database.Database): void {
|
||||
exported ? 1 : 0,
|
||||
async ? 1 : 0,
|
||||
isServerAction ? 1 : 0,
|
||||
isPublic,
|
||||
signature,
|
||||
startLine,
|
||||
endLine
|
||||
@@ -250,6 +267,7 @@ export function scanSymbols(db: Database.Database): void {
|
||||
const signature = extractSignature(decl);
|
||||
const startLine = decl.getStartLineNumber();
|
||||
const endLine = decl.getEndLineNumber();
|
||||
const isPublic = hasPublicTag(node) ? 1 : 0;
|
||||
insertSymbol.run(
|
||||
file.id,
|
||||
name,
|
||||
@@ -257,6 +275,7 @@ export function scanSymbols(db: Database.Database): void {
|
||||
exported ? 1 : 0,
|
||||
0,
|
||||
0,
|
||||
isPublic,
|
||||
signature,
|
||||
startLine,
|
||||
endLine
|
||||
|
||||
@@ -27,6 +27,7 @@ const DDL_STATEMENTS = [
|
||||
is_exported INTEGER DEFAULT 0,
|
||||
is_async INTEGER DEFAULT 0,
|
||||
is_server_action INTEGER DEFAULT 0,
|
||||
is_public INTEGER DEFAULT 0,
|
||||
signature TEXT,
|
||||
start_line INTEGER,
|
||||
end_line INTEGER,
|
||||
|
||||
Reference in New Issue
Block a user