Files
ai_soc_sw/projects/P01_errlens_app/eslint.config.mjs
T
Dev AI fb348f3740 feat(P01): 迁移 Taro 小程序项目代码
- 迁移前端源码 (src/)
- 迁移后端服务 (server/)
- 迁移配置文件 (package.json, tsconfig.json 等)
- 更新需求概要文档
- 更新架构设计文档
- 更新接口定义文档
- 更新环境配置文档
- 创建测试目录结构和配置

项目技术栈:
- Taro 4.1.9 (跨端框架)
- React 18
- TypeScript
- NestJS (后端)
- Tailwind CSS 4
- shadcn/ui 组件库
2026-05-22 16:25:05 +08:00

252 lines
9.8 KiB
JavaScript

import { FlatCompat } from '@eslint/eslintrc';
import { fileURLToPath } from 'node:url';
import path from 'node:path';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const REMOTE_CSS_IMPORT_PATTERN =
/@import\s+(?:url\(\s*['"]?((?:https?:)?\/\/[^'")\s]+)['"]?\s*\)|['"]((?:https?:)?\/\/[^'"\s]+)['"])/g;
const cssImportGuardPlugin = {
processors: {
css: {
preprocess(text) {
const lines = text.split('\n');
const virtualLines = lines.map(line => {
const matches = [...line.matchAll(REMOTE_CSS_IMPORT_PATTERN)];
if (matches.length === 0) {
return '';
}
return matches
.map(match => {
const url = match[1] ?? match[2];
return `__cssExternalImport(${JSON.stringify(url)});`;
})
.join(' ');
});
return [virtualLines.join('\n')];
},
postprocess(messages) {
return messages.flat();
},
supportsAutofix: false,
},
},
};
const baseRestrictedSyntaxRules = [
{
selector: "MemberExpression[object.name='process'][property.name='env']",
message:
'工程规范:请勿在 src 目录下直接使用 process.env\n如需获取 URL 请求前缀,请使用已经注入全局的 PROJECT_DOMAIN',
},
{
selector:
":matches(ExportNamedDeclaration, ExportDefaultDeclaration) :matches([id.name='Network'], [declaration.id.name='Network'])",
message:
"工程规范:禁止自行定义 Network,项目已提供 src/network.ts,请直接使用: import { Network } from '@/network'",
},
{
selector:
'Literal[value=/(^|\\s)(?:[^\\s:]+:)*(bg|text|border|divide|outline|ring|ring-offset|from|to|via|decoration|shadow|accent|caret|fill|stroke)-[a-z0-9-]+\\/([0-9]+|\\[[^\\]]+\\])/], TemplateElement[value.raw=/(^|\\s)(?:[^\\s:]+:)*(bg|text|border|divide|outline|ring|ring-offset|from|to|via|decoration|shadow|accent|caret|fill|stroke)-[a-z0-9-]+\\/([0-9]+|\\[[^\\]]+\\])/]',
message:
'微信小程序兼容性:禁用 Tailwind 颜色不透明度简写(如 bg-primary/10),该语法在微信小程序下 opacity 会丢失。请拆分写(如 bg-primary bg-opacity-10)。',
},
{
selector:
'Literal[value=/(^|\\s)peer-[a-z0-9-]+\\b/], TemplateElement[value.raw=/(^|\\s)peer-[a-z0-9-]+\\b/]',
message:
'微信小程序兼容性:不支持 Tailwind 的 peer-*(如 peer-checked、peer-disabled)。',
},
{
selector:
'Literal[value=/(^|\\s)group-[a-z0-9-]+\\b/], TemplateElement[value.raw=/(^|\\s)group-[a-z0-9-]+\\b/]',
message: '微信小程序兼容性:不支持 Tailwind 的 group-*(如 group-hover)。',
},
{
selector:
'Literal[value=/\\b(?!gap(?:-x|-y)?-)[a-zA-Z0-9-]+\\-[0-9]+\\.[0-9]+\\b/], TemplateElement[value.raw=/\\b(?!gap(?:-x|-y)?-)[a-zA-Z0-9-]+\\-[0-9]+\\.[0-9]+\\b/]',
message:
'微信小程序兼容性:禁用 Tailwind 小数值类名(如 space-y-1.5、w-0.5),请用整数替代(如 space-y-2、w-1)。',
},
{
selector:
":matches(JSXAttribute[name.name='className'], CallExpression[callee.name=/^(cn|cva)$/]) :matches(Literal[value=/\\:has\\(/], TemplateElement[value.raw=/\\:has\\(/])",
message: '微信小程序兼容性:WXSS 不支持 :has(...)(会导致预览上传失败)。',
},
{
selector:
":matches(JSXAttribute[name.name='className'], CallExpression[callee.name=/^(cn|cva)$/]) :matches(Literal[value=/(^|\\s)has-[^\\s]+/], TemplateElement[value.raw=/(^|\\s)has-[^\\s]+/])",
message:
'微信小程序兼容性:禁用 Tailwind 的 has-* 变体(会生成 :has,导致预览上传失败)。',
},
{
selector:
":matches(JSXAttribute[name.name='className'], CallExpression[callee.name=/^(cn|cva)$/]) :matches(Literal[value=/\\[&>\\*/], TemplateElement[value.raw=/\\[&>\\*/])",
message:
'微信小程序兼容性:禁用 [&>*...](可能生成非法 WXSS,如 >:last-child)。请改为 [&>view] 等明确标签。',
},
{
selector:
":matches(JSXAttribute[name.name='className'], CallExpression[callee.name=/^(cn|cva)$/]) :matches(Literal[value=/\\[&[^\\]]*\\[data-/], TemplateElement[value.raw=/\\[&[^\\]]*\\[data-/])",
message:
'微信小程序兼容性:禁用 Tailwind 任意选择器里的属性选择器(如 [&>[data-...]]),可能导致预览上传失败。',
},
{
selector:
":matches(JSXAttribute[name.name='className'], CallExpression[callee.name=/^(cn|cva)$/]) :matches(Literal[value=/\\[[^\\]]*&[^\\]]*~[^\\]]*\\]/], TemplateElement[value.raw=/\\[[^\\]]*&[^\\]]*~[^\\]]*\\]/])",
message: '微信小程序兼容性:WXSS 不支持 ~(会导致预览上传失败)。',
},
{
selector:
"CallExpression[callee.name='__cssExternalImport'] > Literal[value=/^(?:https?:)?\\/\\//]",
message:
'微信小程序兼容性:禁止在 CSS/WXSS 中使用远程 @import(如 Google Fonts)。请改为本地静态资源或删除该导入。',
},
{
selector:
"JSXAttribute[name.name='color'][value.type='Literal'][value.value='currentColor'], JSXAttribute[name.name='color'] > JSXExpressionContainer > Literal[value='currentColor']",
message:
'lucide-react-taro 规范:禁止使用 color="currentColor",小程序端不会按预期继承颜色。请改为显式颜色值,或通过 LucideTaroProvider 提供默认颜色。',
},
];
const pageRestrictedSyntaxRules = [
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Button']",
message:
"组件规范:Button 优先使用 '@/components/ui/button',不要在页面中直接使用 '@tarojs/components' 的 Button。",
},
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Input']",
message:
"组件规范:Input 优先使用 '@/components/ui/input',不要在页面中直接使用 '@tarojs/components' 的 Input。",
},
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Textarea']",
message:
"组件规范:Textarea 优先使用 '@/components/ui/textarea',不要在页面中直接使用 '@tarojs/components' 的 Textarea。",
},
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Label']",
message:
"组件规范:Label 优先使用 '@/components/ui/label',不要在页面中直接使用 '@tarojs/components' 的 Label。",
},
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Switch']",
message:
"组件规范:Switch 优先使用 '@/components/ui/switch',不要在页面中直接使用 '@tarojs/components' 的 Switch。",
},
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Slider']",
message:
"组件规范:Slider 优先使用 '@/components/ui/slider',不要在页面中直接使用 '@tarojs/components' 的 Slider。",
},
{
selector:
"ImportDeclaration[source.value='@tarojs/components'] ImportSpecifier[imported.name='Progress']",
message:
"组件规范:Progress 优先使用 '@/components/ui/progress',不要在页面中直接使用 '@tarojs/components' 的 Progress。",
},
];
const indexPageRestrictedSyntaxRules = [
{
selector: 'JSXText[value=/\\s*应用开发中\\s*/]',
message:
'工程规范:检测到首页 (src/pages/index/index.tsx) 仍为默认占位页面,这会导致用户无法进入新增页面,请根据用户需求开发实际的首页功能与界面。如果已经开发了新的首页,也需要删除旧首页,并更新 src/app.config.ts 文件',
},
];
export default [
...compat.extends('taro/react'),
{
rules: {
'react/jsx-uses-react': 'off',
'react/react-in-jsx-scope': 'off',
'jsx-quotes': ['error', 'prefer-double'],
'react-hooks/exhaustive-deps': 'off',
'tailwindcss/classnames-order': 'off',
'tailwindcss/no-custom-classname': 'off',
},
},
{
files: ['src/**/*.{js,jsx,ts,tsx}'],
ignores: ['src/network.ts'],
rules: {
'no-restricted-syntax': ['error', ...baseRestrictedSyntaxRules],
'no-restricted-properties': [
'error',
{
object: 'Taro',
property: 'request',
message:
"工程规范:请使用 Network.request 替代 Taro.request,导入方式: import { Network } from '@/network'",
},
{
object: 'Taro',
property: 'uploadFile',
message:
"工程规范:请使用 Network.uploadFile 替代 Taro.uploadFile,导入方式: import { Network } from '@/network'",
},
{
object: 'Taro',
property: 'downloadFile',
message:
"工程规范:请使用 Network.downloadFile 替代 Taro.downloadFile,导入方式: import { Network } from '@/network'",
},
],
},
},
{
files: ['src/**/*.css'],
plugins: {
local: cssImportGuardPlugin,
},
processor: 'local/css',
rules: {
'no-undef': 'off',
'no-restricted-syntax': ['error', ...baseRestrictedSyntaxRules],
},
},
{
files: ['src/pages/**/*.tsx'],
rules: {
'no-restricted-syntax': [
'error',
...baseRestrictedSyntaxRules,
...pageRestrictedSyntaxRules,
],
},
},
{
files: ['src/pages/index/index.tsx'],
rules: {
'no-restricted-syntax': [
'error',
...baseRestrictedSyntaxRules,
...pageRestrictedSyntaxRules,
...indexPageRestrictedSyntaxRules,
],
},
},
{
ignores: ['dist/**', 'dist-*/**', 'node_modules/**'],
},
];