# @SkipAssertAccess 装饰器必须单行写，多行会被检查脚本截断

> **日期**: 2026-05-13
> **类型**: 工具链限制（项目特有）

## 现象

`@SkipAssertAccess(...)` 多行写法被 pre-commit hook 当作"未添加装饰器"：

```typescript
// ❌ 这样写不行 — assert-access-check.ts 检测不到
@SkipAssertAccess(
  'updateMany 已用 employeeSlug 过滤，无 IDOR 风险',
)
async revokeCurrent(employeeSlug: string) {
  await this.prisma.token.updateMany({ ... });
}
```

报：`含 prisma.xxx.updateMany( 但未调用 assertAccess，也没有 @SkipAssertAccess('理由')`

## 根因

`testing/scripts/assert-access-check.ts` 收集方法上方装饰器时，**只收集以 `@` 开头的行**：

```typescript
// 行 102-103
if (trimmed.startsWith('@') || trimmed === '' || trimmed.startsWith('//')) {
  if (trimmed.startsWith('@')) decoratorLines.unshift(lines[j]);
}
```

多行 `@SkipAssertAccess(\n  'reason',\n)` 只有第一行 `@SkipAssertAccess(` 被收集，
检测正则 `/@SkipAssertAccess\s*\(\s*['"`][^'"`]+['"`]\s*\)/` 找不到完整括号 → 失败。

## 解决（已验证）

**装饰器写成一行**，理由再长也别换行：

```typescript
// ✅ 这样写通过
@SkipAssertAccess('updateMany 已用 employeeSlug 过滤；caller 从 session 解出；员工只能撤销自己 token，无 IDOR 风险')
async revokeCurrent(employeeSlug: string) { ... }
```

理由长度不限制 — prettier 不会自动拆装饰器实参。

## 适用面

本项目所有 `@SkipAssertAccess(...)` 使用。沉淀此条避免下次再尝试 prettier 风格的多行写法。

## 真正修复（未来可做）

`testing/scripts/assert-access-check.ts` 行 100-107 可改成"装饰器跨行也收集" —
追溯到行首 `@` 然后吞掉到匹配的 `)`。但属于工具增强，与本次实现 PR 解耦，留 chore PR。
