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();
|
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
|
`SELECT s.name, f.path FROM symbols s
|
||||||
JOIN files f ON s.file_id = f.id
|
JOIN files f ON s.file_id = f.id
|
||||||
WHERE s.is_server_action = 1
|
WHERE s.is_server_action = 1
|
||||||
|
AND s.is_public = 0
|
||||||
AND NOT EXISTS (
|
AND NOT EXISTS (
|
||||||
SELECT 1 FROM calls c
|
SELECT 1 FROM calls c
|
||||||
JOIN symbols cs ON c.callee_id = cs.id
|
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")
|
.prepare("SELECT id, path FROM files")
|
||||||
.all() as { id: number; path: string }[];
|
.all() as { id: number; path: string }[];
|
||||||
const insertSymbol = db.prepare(
|
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)
|
`INSERT OR IGNORE INTO symbols (file_id, name, kind, is_exported, is_async, is_server_action, is_public, signature, start_line, end_line)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`
|
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) {
|
for (const file of files) {
|
||||||
let sourceFile;
|
let sourceFile;
|
||||||
try {
|
try {
|
||||||
@@ -223,6 +238,7 @@ export function scanSymbols(db: Database.Database): void {
|
|||||||
const startLine = node.getStartLineNumber();
|
const startLine = node.getStartLineNumber();
|
||||||
const endLine = node.getEndLineNumber();
|
const endLine = node.getEndLineNumber();
|
||||||
const isServerAction = isServerFile && exported && async;
|
const isServerAction = isServerFile && exported && async;
|
||||||
|
const isPublic = hasPublicTag(node) ? 1 : 0;
|
||||||
insertSymbol.run(
|
insertSymbol.run(
|
||||||
file.id,
|
file.id,
|
||||||
name,
|
name,
|
||||||
@@ -230,6 +246,7 @@ export function scanSymbols(db: Database.Database): void {
|
|||||||
exported ? 1 : 0,
|
exported ? 1 : 0,
|
||||||
async ? 1 : 0,
|
async ? 1 : 0,
|
||||||
isServerAction ? 1 : 0,
|
isServerAction ? 1 : 0,
|
||||||
|
isPublic,
|
||||||
signature,
|
signature,
|
||||||
startLine,
|
startLine,
|
||||||
endLine
|
endLine
|
||||||
@@ -250,6 +267,7 @@ export function scanSymbols(db: Database.Database): void {
|
|||||||
const signature = extractSignature(decl);
|
const signature = extractSignature(decl);
|
||||||
const startLine = decl.getStartLineNumber();
|
const startLine = decl.getStartLineNumber();
|
||||||
const endLine = decl.getEndLineNumber();
|
const endLine = decl.getEndLineNumber();
|
||||||
|
const isPublic = hasPublicTag(node) ? 1 : 0;
|
||||||
insertSymbol.run(
|
insertSymbol.run(
|
||||||
file.id,
|
file.id,
|
||||||
name,
|
name,
|
||||||
@@ -257,6 +275,7 @@ export function scanSymbols(db: Database.Database): void {
|
|||||||
exported ? 1 : 0,
|
exported ? 1 : 0,
|
||||||
0,
|
0,
|
||||||
0,
|
0,
|
||||||
|
isPublic,
|
||||||
signature,
|
signature,
|
||||||
startLine,
|
startLine,
|
||||||
endLine
|
endLine
|
||||||
|
|||||||
@@ -27,6 +27,7 @@ const DDL_STATEMENTS = [
|
|||||||
is_exported INTEGER DEFAULT 0,
|
is_exported INTEGER DEFAULT 0,
|
||||||
is_async INTEGER DEFAULT 0,
|
is_async INTEGER DEFAULT 0,
|
||||||
is_server_action INTEGER DEFAULT 0,
|
is_server_action INTEGER DEFAULT 0,
|
||||||
|
is_public INTEGER DEFAULT 0,
|
||||||
signature TEXT,
|
signature TEXT,
|
||||||
start_line INTEGER,
|
start_line INTEGER,
|
||||||
end_line INTEGER,
|
end_line INTEGER,
|
||||||
|
|||||||
Reference in New Issue
Block a user