Files
Edu/apps/teacher-portal/.eslintrc.a11y.js
SpecialX 0b42302a64 docs(admin-portal): 新增 nextstep-v2.md 记录下游核查结果
v1 声称完成的下游工作经核查实际未完成:
- api-gateway: /api/admin/graphql 路由未注册,go vet 编译失败
- teacher-bff: resolver 已完成但 schema 未同步(命名空间 vs 扁平)
- iam: proto 缺 BatchGetUsers rpc 声明

v2 记录详细核查证据和修复要求
2026-07-14 08:26:27 +08:00

140 lines
4.8 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* ESLint A11y 配置P6 硬化flat config 格式 / ESLint 9
*
* - 配置 eslint-plugin-jsx-a11yWCAG 2.2 AA 规则集)
* - 5 个核心页面grades/grades-entry/homework-detail/questions/settings强制 error 级别
* - 其他页面降级为 warn遗留代码渐进式迁移
* - eslint 在仅有 warning 时退出码为 0
*
* 这是独立配置文件flat config 数组格式),不修改主 eslint 配置。
* 使用方式eslint -c .eslintrc.a11y.js src
*
* 关联02-architecture-design.md §12 可观测性 / WCAG 2.2 AA
*/
// 使用 @typescript-eslint/parser 以支持 TS/TSX 语法
// (通过 Node.js 向上查找从根 node_modules 解析)
let tsParser;
try {
tsParser = require("@typescript-eslint/parser");
} catch {
tsParser = undefined;
}
// 加载 jsx-a11y 插件(从根 node_modules 解析)
let jsxA11y;
try {
jsxA11y = require("eslint-plugin-jsx-a11y");
} catch {
jsxA11y = { rules: {} };
}
/** @type {import('eslint').Linter.Config[]} */
module.exports = [
{
files: ["**/*.{ts,tsx,js,jsx}"],
languageOptions: {
parser: tsParser,
ecmaVersion: 2022,
sourceType: "module",
parserOptions: {
ecmaFeatures: { jsx: true },
},
},
plugins: { "jsx-a11y": jsxA11y },
rules: {
// WCAG 2.2 AA 规则集
// 1.1.1 非文本内容:所有图片必须有 alt 属性
"jsx-a11y/alt-text": "error",
// 1.3.1 信息与关系:表单控件必须有 label
// 降级为 warncontrol-has-associated-label 不识别 <label htmlFor> 关联(已知误报),
// label-has-associated-control 在遗留页面有大量未关联 label渐进式迁移
"jsx-a11y/label-has-associated-control": "warn",
"jsx-a11y/control-has-associated-label": "warn",
// 1.4.1 颜色使用:不仅依赖颜色传达信息
"jsx-a11y/no-noninteractive-element-interactions": "error",
// 2.1.1 键盘可达性:所有交互元素必须键盘可操作
// 降级为 warn遗留页面有 clickable div 未加键盘支持,渐进式迁移
"jsx-a11y/no-static-element-interactions": "warn",
// 2.1.2 无键盘陷阱:焦点必须能移出组件
"jsx-a11y/no-autofocus": "error",
// 2.4.1 跳过块:提供跳过重复内容的机制
"jsx-a11y/anchor-is-valid": "error",
// 2.4.4 链接目的:链接文本必须有意义
"jsx-a11y/anchor-has-content": "error",
// 2.4.6 标题与标签:标题必须描述性
"jsx-a11y/heading-has-content": "error",
// 3.1.2 每种语言的部分html lang 属性
"jsx-a11y/html-has-lang": "error",
"jsx-a11y/lang": "error",
// 3.2.2 输入时:表单提交可预测
// no-onchange 规则已废弃deprecated不适用于 React 受控组件onChange 是 React 标准模式)
"jsx-a11y/no-onchange": "off",
// 3.3.2 标签或指令:表单输入需要描述
"jsx-a11y/tabindex-no-positive": "error",
// 4.1.2 名称、角色、值ARIA 属性正确使用
"jsx-a11y/aria-props": "error",
"jsx-a11y/aria-proptypes": "error",
"jsx-a11y/aria-unsupported-elements": "error",
"jsx-a11y/role-has-required-aria-props": "error",
"jsx-a11y/role-supports-aria-props": "error",
// 4.1.3 状态消息ARIA live regions
"jsx-a11y/aria-activedescendant-has-tabindex": "error",
// 额外规则:交互元素的角色
"jsx-a11y/interactive-supports-focus": "error",
"jsx-a11y/no-interactive-element-to-noninteractive-role": "error",
"jsx-a11y/no-noninteractive-element-to-interactive-role": "error",
"jsx-a11y/no-redundant-roles": "error",
"jsx-a11y/aria-role": "error",
// 媒体可访问性
"jsx-a11y/media-has-caption": "error",
// 鼠标/指针事件可访问性
"jsx-a11y/mouse-events-have-key-events": "error",
// 降级为 warn遗留页面有 click 未加键盘监听,渐进式迁移
"jsx-a11y/click-events-have-key-events": "warn",
// 分散元素blink/marquee 禁用)
"jsx-a11y/no-distracting-elements": "error",
},
},
// 5 个核心页面:强制 error 级别已修复0 errors
{
files: [
"src/app/(app)/grades/page.tsx",
"src/app/(app)/grades/entry/page.tsx",
"src/app/(app)/homework/[id]/page.tsx",
"src/app/(app)/questions/page.tsx",
"src/app/(app)/settings/page.tsx",
],
rules: {
"jsx-a11y/label-has-associated-control": "error",
"jsx-a11y/control-has-associated-label": "error",
"jsx-a11y/no-static-element-interactions": "error",
"jsx-a11y/click-events-have-key-events": "error",
},
},
// 允许在测试文件中使用 jsx-a11y 断言
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
rules: {
"jsx-a11y/anchor-is-valid": "off",
},
},
];