清理 lint 剩余告警并更新审计文档状态

This commit is contained in:
2026-03-03 13:53:19 +08:00
parent d91ea8c1e8
commit 482307f2f4
21 changed files with 33 additions and 27 deletions
+11 -4
View File
@@ -153,9 +153,10 @@
- 修复内容: - 修复内容:
- 修复 `GlobalUserBadge``RestaurantCard``SwipeableCard``PageTransition``useGeolocation` 的 hooks 规则 error - 修复 `GlobalUserBadge``RestaurantCard``SwipeableCard``PageTransition``useGeolocation` 的 hooks 规则 error
- 修复页面文案中的未转义引号(`react/no-unescaped-entities`); - 修复页面文案中的未转义引号(`react/no-unescaped-entities`);
- `npm run lint` 已恢复为 0 error(仍有 warning,后续可持续清理) - 第二轮清理补齐所有剩余 warning(未使用变量、无效 eslint-disable、hooks 依赖、`no-img-element` 策略化处理)
- `npm run lint` 已恢复为 0 error / 0 warning。
- 证据: - 证据:
- 修复后执行 `npm run lint``0 errors / 29 warnings` - 修复后执行 `npm run lint``0 errors / 0 warnings`
- 代表性问题: - 代表性问题:
- `src/components/SwipeableCard.tsx:81`render 阶段注册副作用) - `src/components/SwipeableCard.tsx:81`render 阶段注册副作用)
- `src/components/GlobalUserBadge.tsx:23`effect 内同步 setState - `src/components/GlobalUserBadge.tsx:23`effect 内同步 setState
@@ -241,6 +242,12 @@
--- ---
## 第二轮执行进展(2026-03-03
- 任务 2(清理 lint warning):✅ 已完成
- 结果:`npm run lint` => `0 errors / 0 warnings`
---
## 基线执行结果(本次审查) ## 基线执行结果(本次审查)
- `npm run lint`:失败,`10 errors / 32 warnings` - `npm run lint`:失败,`10 errors / 32 warnings`
- `npm test`:通过,`53 files / 329 tests`,但有 `act(...)` 警告。 - `npm test`:通过,`53 files / 329 tests`,但有 `act(...)` 警告。
@@ -257,5 +264,5 @@
4. 最后推进 R 类重构(N+1、增量更新、API client 统一)。 4. 最后推进 R 类重构(N+1、增量更新、API client 统一)。
## 交付说明 ## 交付说明
- 本文档静态审查结论,未改动业务代码 - 本文档已从“静态审查结论”演进为“修复跟踪文档”,会随每轮修复持续更新状态
- 建议下一步按“P0/P1 修复 PR + 回归测试补齐”方式分批落地 - 现阶段建议继续按“问题分组 + 回归测试同步补齐”的节奏推进
-1
View File
@@ -1,5 +1,4 @@
import sharp from "sharp"; import sharp from "sharp";
import { mkdirSync } from "fs";
import { join, dirname } from "path"; import { join, dirname } from "path";
import { fileURLToPath } from "url"; import { fileURLToPath } from "url";
+1 -1
View File
@@ -1,5 +1,5 @@
import { vi } from "vitest"; import { vi } from "vitest";
import { mockDeep, mockReset, type DeepMockProxy } from "vitest-mock-extended"; import { mockDeep, mockReset } from "vitest-mock-extended";
import type { PrismaClient } from "@prisma/client"; import type { PrismaClient } from "@prisma/client";
export const prismaMock = mockDeep<PrismaClient>(); export const prismaMock = mockDeep<PrismaClient>();
@@ -1,7 +1,7 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { prismaMock, resetPrismaMock } from "@/__tests__/helpers/prisma-mock"; import { prismaMock, resetPrismaMock } from "@/__tests__/helpers/prisma-mock";
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils"; import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
import { TEST_BLINDBOX_ROOM, TEST_USER } from "@/__tests__/helpers/fixtures"; import { TEST_BLINDBOX_ROOM } from "@/__tests__/helpers/fixtures";
vi.mock("@/lib/auth", () => ({ vi.mock("@/lib/auth", () => ({
getAuthUserId: vi.fn().mockResolvedValue("user-1"), getAuthUserId: vi.fn().mockResolvedValue("user-1"),
+2 -2
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils"; import { createRequest, createRouteContext } from "@/__tests__/helpers/api-test-utils";
import { TEST_ROOM_DATA, TEST_RESTAURANT } from "@/__tests__/helpers/fixtures"; import { TEST_ROOM_DATA } from "@/__tests__/helpers/fixtures";
vi.mock("@/lib/prisma", () => ({ prisma: {} })); vi.mock("@/lib/prisma", () => ({ prisma: {} }));
+1 -1
View File
@@ -1,6 +1,6 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils"; import { createRequest, createRouteContext, parseJsonResponse } from "@/__tests__/helpers/api-test-utils";
import { TEST_ROOM_DATA, TEST_RESTAURANT, TEST_RESTAURANT_2 } from "@/__tests__/helpers/fixtures"; import { TEST_ROOM_DATA, TEST_RESTAURANT } from "@/__tests__/helpers/fixtures";
vi.mock("@/lib/prisma", () => ({ prisma: {} })); vi.mock("@/lib/prisma", () => ({ prisma: {} }));
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi, beforeEach } from "vitest";
import { render, screen, waitFor } from "@testing-library/react"; import { render, waitFor } from "@testing-library/react";
import React from "react"; import React from "react";
import { ToastContext, type ToastContextValue } from "@/hooks/useToast"; import { ToastContext, type ToastContextValue } from "@/hooks/useToast";
+6 -2
View File
@@ -121,7 +121,11 @@ function RoomCard({
tabIndex={0} tabIndex={0}
onClick={(e) => { onClick={(e) => {
e.stopPropagation(); e.stopPropagation();
confirmDelete ? onCancelDelete() : onRequestDelete(); if (confirmDelete) {
onCancelDelete();
} else {
onRequestDelete();
}
}} }}
className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted/40 transition-colors hover:bg-elevated hover:text-muted active:text-foreground" className="flex h-7 w-7 shrink-0 items-center justify-center rounded-full text-muted/40 transition-colors hover:bg-elevated hover:text-muted active:text-foreground"
> >
@@ -276,7 +280,7 @@ export default function BlindboxLobbyPage() {
} finally { } finally {
setDeletingId(null); setDeletingId(null);
} }
}, [deletingId, profile, toast]); }, [deletingId, profile, toast, mutateRooms]);
return ( return (
<div className="relative flex min-h-dvh flex-col items-center justify-center bg-background px-6 py-6 overflow-y-auto scrollbar-none"> <div className="relative flex min-h-dvh flex-col items-center justify-center bg-background px-6 py-6 overflow-y-auto scrollbar-none">
+2 -1
View File
@@ -1,6 +1,7 @@
"use client"; "use client";
import { useEffect } from "react"; import { useEffect } from "react";
import Image from "next/image";
export default function GlobalError({ export default function GlobalError({
error, error,
@@ -17,7 +18,7 @@ export default function GlobalError({
<html lang="zh-CN"> <html lang="zh-CN">
<body style={{ margin: 0, fontFamily: "system-ui, sans-serif", background: "#0a0a0a", color: "#e5e5e5" }}> <body style={{ margin: 0, fontFamily: "system-ui, sans-serif", background: "#0a0a0a", color: "#e5e5e5" }}>
<div style={{ display: "flex", minHeight: "100dvh", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "1.5rem" }}> <div style={{ display: "flex", minHeight: "100dvh", flexDirection: "column", alignItems: "center", justifyContent: "center", padding: "1.5rem" }}>
<img src="/error-robot.png" alt="错误" style={{ width: 120, height: 120 }} /> <Image src="/error-robot.png" alt="错误" width={120} height={120} priority />
<h1 style={{ marginTop: "1.5rem", fontSize: "1.25rem", fontWeight: 700 }}></h1> <h1 style={{ marginTop: "1.5rem", fontSize: "1.25rem", fontWeight: 700 }}></h1>
<p style={{ marginTop: "0.5rem", fontSize: "0.875rem", color: "#a3a3a3", textAlign: "center" }}> <p style={{ marginTop: "0.5rem", fontSize: "0.875rem", color: "#a3a3a3", textAlign: "center" }}>
-3
View File
@@ -10,7 +10,6 @@ import Button from "@/components/Button";
import { useRoomPolling } from "@/hooks/useRoomPolling"; import { useRoomPolling } from "@/hooks/useRoomPolling";
import { getUserId } from "@/lib/userId"; import { getUserId } from "@/lib/userId";
import { joinRoom } from "@/lib/room"; import { joinRoom } from "@/lib/room";
import { getSceneConfig } from "@/lib/sceneConfig";
import { useToast } from "@/hooks/useToast"; import { useToast } from "@/hooks/useToast";
export default function RoomPage() { export default function RoomPage() {
@@ -126,8 +125,6 @@ export default function RoomPage() {
const initialIndex = swipeCounts[userId] ?? 0; const initialIndex = swipeCounts[userId] ?? 0;
const ready = joined && userId && restaurants.length > 0; const ready = joined && userId && restaurants.length > 0;
const sceneConfig = getSceneConfig(scene);
if (!ready) { if (!ready) {
return <SwipeDeckSkeleton />; return <SwipeDeckSkeleton />;
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi } from "vitest"; import { describe, it, expect } from "vitest";
import { render, screen } from "@testing-library/react"; import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event"; import userEvent from "@testing-library/user-event";
import Input from "./Input"; import Input from "./Input";
+1 -1
View File
@@ -23,7 +23,7 @@ vi.mock("./ShareCardModal", () => ({
})); }));
vi.mock("./RestaurantImage", () => ({ vi.mock("./RestaurantImage", () => ({
default: ({ alt }: { alt: string }) => <img alt={alt} />, default: ({ alt }: { alt: string }) => <div role="img" aria-label={alt} />,
})); }));
vi.mock("./AuthModal", () => ({ vi.mock("./AuthModal", () => ({
+1
View File
@@ -1,4 +1,5 @@
"use client"; "use client";
/* eslint-disable @next/next/no-img-element -- Uses raw img for unrestricted external sources and card capture fidelity. */
import { useState, useCallback } from "react"; import { useState, useCallback } from "react";
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element -- Share card rendering requires direct img tags for html-to-image output. */
import { Star, MapPin, Zap } from "lucide-react"; import { Star, MapPin, Zap } from "lucide-react";
import type { Restaurant, MatchType, SceneType } from "@/types"; import type { Restaurant, MatchType, SceneType } from "@/types";
import { getSceneConfig } from "@/lib/sceneConfig"; import { getSceneConfig } from "@/lib/sceneConfig";
-2
View File
@@ -83,8 +83,6 @@ export default function RoomManageModal({
[roomId, userId, toast, onClose], [roomId, userId, toast, onClose],
); );
const otherUsers = users.filter((u) => u !== userId);
return ( return (
<Modal open={open} onClose={onClose}> <Modal open={open} onClose={onClose}>
<button <button
+1
View File
@@ -1,3 +1,4 @@
/* eslint-disable @next/next/no-img-element -- Share card rendering requires direct img tags for html-to-image output. */
import { QRCodeSVG } from "qrcode.react"; import { QRCodeSVG } from "qrcode.react";
import type { ReactNode } from "react"; import type { ReactNode } from "react";
-1
View File
@@ -299,7 +299,6 @@ export default function SwipeDeck({
if (serverIndex === 0 && currentIndexRef.current > 0 && !resetting) { if (serverIndex === 0 && currentIndexRef.current > 0 && !resetting) {
clearLocalState(); clearLocalState();
} }
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [swipeCounts, userId, resetting, clearLocalState]); }, [swipeCounts, userId, resetting, clearLocalState]);
const handleReset = useCallback(async () => { const handleReset = useCallback(async () => {
+1 -2
View File
@@ -5,7 +5,6 @@ import { QrCode, LogOut, Crown, Lock } from "lucide-react";
import QrInviteModal from "./QrInviteModal"; import QrInviteModal from "./QrInviteModal";
import RoomManageModal from "./RoomManageModal"; import RoomManageModal from "./RoomManageModal";
import type { UserProfile, SceneType } from "@/types"; import type { UserProfile, SceneType } from "@/types";
import { getSceneConfig } from "@/lib/sceneConfig";
interface TopNavProps { interface TopNavProps {
roomId: string; roomId: string;
@@ -34,7 +33,6 @@ export default function TopNav({
userProfiles = {}, userProfiles = {},
scene = "eat", scene = "eat",
}: TopNavProps) { }: TopNavProps) {
const sceneConfig = getSceneConfig(scene);
const [showQr, setShowQr] = useState(false); const [showQr, setShowQr] = useState(false);
const [showManage, setShowManage] = useState(false); const [showManage, setShowManage] = useState(false);
@@ -51,6 +49,7 @@ export default function TopNav({
</button> </button>
<button <button
onClick={() => setShowQr(true)} onClick={() => setShowQr(true)}
aria-label={`邀请二维码(${roomId}${typeof userCount === "number" ? `,当前 ${userCount}` : ""}`}
className="flex items-center gap-1 rounded-full bg-accent/15 px-2.5 py-1 text-xs font-semibold text-accent transition-colors active:bg-accent/25" className="flex items-center gap-1 rounded-full bg-accent/15 px-2.5 py-1 text-xs font-semibold text-accent transition-colors active:bg-accent/25"
> >
<QrCode size={13} /> <QrCode size={13} />
+1 -1
View File
@@ -1,5 +1,5 @@
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { renderHook, waitFor, act } from "@testing-library/react"; import { renderHook } from "@testing-library/react";
let esInstances: { let esInstances: {
url: string; url: string;
+1 -1
View File
@@ -1,4 +1,4 @@
import { describe, it, expect, vi, beforeEach } from "vitest"; import { describe, it, expect, vi } from "vitest";
import { NextRequest, NextResponse } from "next/server"; import { NextRequest, NextResponse } from "next/server";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { ApiError, requireUserId, apiHandler } from "@/lib/api"; import { ApiError, requireUserId, apiHandler } from "@/lib/api";
-1
View File
@@ -4,7 +4,6 @@ import {
TEST_USER_2, TEST_USER_2,
TEST_RESTAURANT, TEST_RESTAURANT,
TEST_RESTAURANT_2, TEST_RESTAURANT_2,
TEST_RESTAURANT_3,
TEST_ROOM_DATA, TEST_ROOM_DATA,
} from "@/__tests__/helpers/fixtures"; } from "@/__tests__/helpers/fixtures";