@lark-apaas/fullstack-presets 1.1.8 → 1.1.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,4 +2,5 @@ export declare const customRules: {
2
2
  'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
3
3
  'require-app-container': import("eslint").Rule.RuleModule;
4
4
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
5
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
5
6
  };
@@ -7,8 +7,10 @@ exports.customRules = void 0;
7
7
  const no_nested_styled_jsx_1 = __importDefault(require("./no-nested-styled-jsx"));
8
8
  const require_app_container_1 = __importDefault(require("./require-app-container"));
9
9
  const no_direct_capability_api_1 = __importDefault(require("./no-direct-capability-api"));
10
+ const require_scroll_reveal_hook_1 = __importDefault(require("./require-scroll-reveal-hook"));
10
11
  exports.customRules = {
11
12
  'no-nested-styled-jsx': no_nested_styled_jsx_1.default,
12
13
  'require-app-container': require_app_container_1.default,
13
14
  'no-direct-capability-api': no_direct_capability_api_1.default,
15
+ 'require-scroll-reveal-hook': require_scroll_reveal_hook_1.default,
14
16
  };
@@ -0,0 +1,12 @@
1
+ /**
2
+ * ESLint rule to disallow Welcome or PagePlaceholder components as the default home page route.
3
+ *
4
+ * This rule enforces that the first-level index route (the actual home page at "/")
5
+ * should use an actual page component rather than placeholder components from the framework.
6
+ *
7
+ * Only checks the first-level index route nested directly under a parent Route (typically Layout).
8
+ * Only flags Welcome/PagePlaceholder if they are imported from @lark-apaas/client-toolkit.
9
+ */
10
+ import type { Rule } from 'eslint';
11
+ declare const rule: Rule.RuleModule;
12
+ export default rule;
@@ -0,0 +1,123 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const rule = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: 'Disallow Welcome or PagePlaceholder from @lark-apaas/client-toolkit as the default home page route element',
8
+ category: 'Best Practices',
9
+ recommended: true,
10
+ },
11
+ messages: {
12
+ noWelcomeIndex: 'Index route should not use {{componentName}} from @lark-apaas/client-toolkit as the element. Please create and use your own home page component (e.g., HomePage, Dashboard).',
13
+ },
14
+ schema: [],
15
+ },
16
+ create(context) {
17
+ // Track imports from @lark-apaas/client-toolkit
18
+ const toolkitImports = new Set();
19
+ return {
20
+ ImportDeclaration(node) {
21
+ const importNode = node;
22
+ const source = importNode.source?.value;
23
+ // Check if importing from @lark-apaas/client-toolkit (any subpath)
24
+ if (typeof source === 'string' &&
25
+ source.startsWith('@lark-apaas/client-toolkit')) {
26
+ // Collect all imported names
27
+ importNode.specifiers?.forEach(specifier => {
28
+ if (specifier.type === 'ImportSpecifier' &&
29
+ specifier.imported?.name) {
30
+ toolkitImports.add(specifier.imported.name);
31
+ }
32
+ });
33
+ }
34
+ },
35
+ JSXElement(node) {
36
+ const element = node;
37
+ const openingElement = element.openingElement;
38
+ if (!openingElement)
39
+ return;
40
+ const elementName = openingElement.name;
41
+ // Check if this is a <Route> element
42
+ if (elementName.type !== 'JSXIdentifier' ||
43
+ elementName.name !== 'Route') {
44
+ return;
45
+ }
46
+ // Check if it has an index prop that is truthy
47
+ const indexAttr = openingElement.attributes.find(attr => attr.type === 'JSXAttribute' &&
48
+ attr.name?.type === 'JSXIdentifier' &&
49
+ attr.name?.name === 'index');
50
+ // If no index attribute, skip this route
51
+ if (!indexAttr) {
52
+ return;
53
+ }
54
+ // Check if index={false} explicitly
55
+ const indexValue = indexAttr.value;
56
+ if (indexValue?.type === 'JSXExpressionContainer' &&
57
+ indexValue.expression?.type === 'Literal' &&
58
+ indexValue.expression.value === false) {
59
+ return;
60
+ }
61
+ // Check if this is a first-level index route (nested under exactly one parent Route)
62
+ // The structure should be: <Routes> -> <Route element={<Layout />}> -> <Route index>
63
+ let routeParentCount = 0;
64
+ let current = element.parent;
65
+ while (current) {
66
+ if (current.type === 'JSXElement') {
67
+ const currentElement = current;
68
+ const currentOpeningElement = currentElement.openingElement;
69
+ const currentElementName = currentOpeningElement?.name;
70
+ if (currentElementName?.type === 'JSXIdentifier' &&
71
+ currentElementName.name === 'Route') {
72
+ routeParentCount++;
73
+ }
74
+ else if (currentElementName?.type === 'JSXIdentifier' &&
75
+ currentElementName.name === 'Routes') {
76
+ // Stop when we reach Routes
77
+ break;
78
+ }
79
+ }
80
+ current = current.parent;
81
+ }
82
+ // Only check if this is a first-level route (exactly 1 parent Route)
83
+ // This corresponds to the default home page pattern:
84
+ // <Routes><Route element={<Layout />}><Route index /></Route></Routes>
85
+ if (routeParentCount !== 1) {
86
+ return;
87
+ }
88
+ // Find the element prop
89
+ const elementProp = openingElement.attributes.find(attr => attr.type === 'JSXAttribute' &&
90
+ attr.name?.type === 'JSXIdentifier' &&
91
+ attr.name?.name === 'element');
92
+ if (!elementProp) {
93
+ return;
94
+ }
95
+ // Check if the element prop value is a JSXExpressionContainer with a JSXElement
96
+ const propValue = elementProp.value;
97
+ if (propValue?.type === 'JSXExpressionContainer' &&
98
+ propValue.expression?.type === 'JSXElement') {
99
+ const jsxElement = propValue.expression;
100
+ const jsxOpeningElement = jsxElement.openingElement;
101
+ const jsxElementName = jsxOpeningElement?.name;
102
+ // Check if the component name is Welcome or PagePlaceholder
103
+ if (jsxElementName?.type === 'JSXIdentifier' &&
104
+ (jsxElementName.name === 'Welcome' ||
105
+ jsxElementName.name === 'PagePlaceholder')) {
106
+ const componentName = jsxElementName.name;
107
+ // Only report if this component was imported from @lark-apaas/client-toolkit
108
+ if (toolkitImports.has(componentName)) {
109
+ context.report({
110
+ node: elementProp,
111
+ messageId: 'noWelcomeIndex',
112
+ data: {
113
+ componentName,
114
+ },
115
+ });
116
+ }
117
+ }
118
+ }
119
+ },
120
+ };
121
+ },
122
+ };
123
+ exports.default = rule;
@@ -0,0 +1,14 @@
1
+ /**
2
+ * ESLint rule to require a default index route in RoutesComponent.
3
+ *
4
+ * This rule enforces that the RoutesComponent must have a first-level index route
5
+ * to handle the default "/" path. This ensures users don't see a 404 when visiting
6
+ * the application root.
7
+ *
8
+ * Valid configurations:
9
+ * 1. <Route index element={...} /> (first-level, nested under one parent Route)
10
+ * 2. <Route path="/" element={...} /> (first-level or top-level)
11
+ */
12
+ import type { Rule } from 'eslint';
13
+ declare const rule: Rule.RuleModule;
14
+ export default rule;
@@ -0,0 +1,98 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const rule = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: 'Require a default index route in RoutesComponent to handle the root path',
8
+ category: 'Best Practices',
9
+ recommended: true,
10
+ },
11
+ messages: {
12
+ missingIndexRoute: 'RoutesComponent is missing a default home page route. Add either:\n - <Route index element={<HomePage />} /> (nested under Layout)\n - <Route path="/" element={<HomePage />} /> (at any level)',
13
+ },
14
+ schema: [],
15
+ },
16
+ create(context) {
17
+ let inRoutesComponent = false;
18
+ let hasDefaultRoute = false;
19
+ let routeDepth = 0;
20
+ return {
21
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
22
+ VariableDeclarator(node) {
23
+ // Check if this is RoutesComponent
24
+ if (node.id?.name === 'RoutesComponent') {
25
+ inRoutesComponent = true;
26
+ hasDefaultRoute = false;
27
+ routeDepth = 0;
28
+ }
29
+ },
30
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
31
+ 'VariableDeclarator:exit'(node) {
32
+ if (node.id?.name === 'RoutesComponent') {
33
+ if (!hasDefaultRoute) {
34
+ context.report({
35
+ node,
36
+ messageId: 'missingIndexRoute',
37
+ });
38
+ }
39
+ inRoutesComponent = false;
40
+ }
41
+ },
42
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
43
+ JSXElement(node) {
44
+ if (!inRoutesComponent)
45
+ return;
46
+ const elementName = node.openingElement?.name?.name;
47
+ // Track Route depth
48
+ if (elementName === 'Route') {
49
+ routeDepth++;
50
+ // Only check first-level routes (depth 1 or 2)
51
+ // depth 1: <Routes><Route index /></Routes>
52
+ // depth 2: <Routes><Route><Route index /></Route></Routes>
53
+ if (routeDepth <= 2) {
54
+ const attributes = node.openingElement?.attributes || [];
55
+ // Check for index attribute
56
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
57
+ const indexAttr = attributes.find((attr) => attr.type === 'JSXAttribute' && attr.name?.name === 'index');
58
+ if (indexAttr) {
59
+ // Make sure it's not index={false}
60
+ const isIndexFalse = indexAttr.value?.type === 'JSXExpressionContainer' &&
61
+ indexAttr.value.expression?.type === 'Literal' &&
62
+ indexAttr.value.expression.value === false;
63
+ if (!isIndexFalse) {
64
+ hasDefaultRoute = true;
65
+ }
66
+ }
67
+ // Check for path="/"
68
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
69
+ const pathAttr = attributes.find((attr) => attr.type === 'JSXAttribute' && attr.name?.name === 'path');
70
+ if (pathAttr) {
71
+ const value = pathAttr.value;
72
+ // Handle path="/"
73
+ if (value?.type === 'Literal' && value.value === '/') {
74
+ hasDefaultRoute = true;
75
+ }
76
+ // Handle path={"/"}
77
+ if (value?.type === 'JSXExpressionContainer' &&
78
+ value.expression?.type === 'Literal' &&
79
+ value.expression.value === '/') {
80
+ hasDefaultRoute = true;
81
+ }
82
+ }
83
+ }
84
+ }
85
+ },
86
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
87
+ 'JSXElement:exit'(node) {
88
+ if (!inRoutesComponent)
89
+ return;
90
+ const elementName = node.openingElement?.name?.name;
91
+ if (elementName === 'Route') {
92
+ routeDepth--;
93
+ }
94
+ },
95
+ };
96
+ },
97
+ };
98
+ exports.default = rule;
@@ -0,0 +1,17 @@
1
+ /**
2
+ * ESLint 规则:使用 scroll-reveal 样式类时必须调用 useScrollReveal hook
3
+ *
4
+ * 此规则确保开发者在使用 scroll-reveal 滚动动画样式时,
5
+ * 正确导入并调用 useScrollReveal hook 来启用动画效果。
6
+ *
7
+ * 错误示例:
8
+ * <div className="scroll-reveal">Content</div> // 未使用 hook
9
+ *
10
+ * 正确示例:
11
+ * import { useScrollReveal } from '@lark-apaas/client-toolkit/hooks/useScrollReveal';
12
+ * useScrollReveal(); // 无需参数,默认监听整个页面
13
+ * return <div className="scroll-reveal">Content</div>
14
+ */
15
+ import type { Rule } from 'eslint';
16
+ declare const rule: Rule.RuleModule;
17
+ export default rule;
@@ -0,0 +1,135 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const DEFAULT_MESSAGE = "Elements with 'scroll-reveal' class require the useScrollReveal hook. " +
4
+ "Import and call useScrollReveal() from '@lark-apaas/client-toolkit/hooks/useScrollReveal'.";
5
+ // 匹配 className 中的 scroll-reveal 类
6
+ const SCROLL_REVEAL_PATTERN = /(^|\s)scroll-reveal(\s|$)/;
7
+ // Hook 相关常量
8
+ const HOOK_NAME = 'useScrollReveal';
9
+ const HOOK_IMPORT_SOURCE = '@lark-apaas/client-toolkit/hooks/useScrollReveal';
10
+ /**
11
+ * 从 AST 节点中提取 className 字符串值
12
+ * 支持:字符串字面量、模板字符串、字符串拼接、JSX 表达式容器
13
+ */
14
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
15
+ function extractClassNameValue(node) {
16
+ if (!node)
17
+ return null;
18
+ switch (node.type) {
19
+ case 'Literal':
20
+ return typeof node.value === 'string' ? node.value : null;
21
+ case 'TemplateLiteral':
22
+ // 拼接所有静态部分(quasis)
23
+ return node.quasis
24
+ .map((q) => q.value.raw)
25
+ .join('');
26
+ case 'BinaryExpression':
27
+ // 处理字符串拼接 'a' + 'b'
28
+ if (node.operator === '+') {
29
+ const left = extractClassNameValue(node.left);
30
+ const right = extractClassNameValue(node.right);
31
+ if (left !== null || right !== null) {
32
+ return (left || '') + (right || '');
33
+ }
34
+ }
35
+ return null;
36
+ case 'JSXExpressionContainer':
37
+ return extractClassNameValue(node.expression);
38
+ default:
39
+ return null;
40
+ }
41
+ }
42
+ const rule = {
43
+ meta: {
44
+ type: 'problem',
45
+ docs: {
46
+ description: '使用 scroll-reveal 样式类时必须导入并调用 useScrollReveal hook',
47
+ category: 'Best Practices',
48
+ recommended: true,
49
+ },
50
+ schema: [
51
+ {
52
+ type: 'object',
53
+ properties: {
54
+ message: {
55
+ type: 'string',
56
+ description: '自定义错误信息',
57
+ },
58
+ },
59
+ additionalProperties: false,
60
+ },
61
+ ],
62
+ messages: {
63
+ requireScrollRevealHook: DEFAULT_MESSAGE,
64
+ },
65
+ },
66
+ create(context) {
67
+ const options = context.options[0];
68
+ const customMessage = options?.message;
69
+ // 文件级别的状态追踪
70
+ let hasScrollRevealClass = false;
71
+ let hasHookImport = false;
72
+ let hasHookCall = false;
73
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
+ let scrollRevealNodes = []; // 保存有 scroll-reveal 的节点用于报错定位
75
+ return {
76
+ // 检测 JSX 中的 className 属性
77
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
+ JSXAttribute(node) {
79
+ if (node.name?.name !== 'className')
80
+ return;
81
+ const classValue = extractClassNameValue(node.value);
82
+ if (classValue && SCROLL_REVEAL_PATTERN.test(classValue)) {
83
+ hasScrollRevealClass = true;
84
+ scrollRevealNodes.push(node);
85
+ }
86
+ },
87
+ // 检测 useScrollReveal 导入
88
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
89
+ ImportDeclaration(node) {
90
+ const source = node.source?.value;
91
+ if (typeof source !== 'string')
92
+ return;
93
+ // 检查是否从正确的源导入
94
+ if (source === HOOK_IMPORT_SOURCE ||
95
+ source.includes('useScrollReveal')) {
96
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
97
+ const hasHookSpecifier = node.specifiers?.some((spec) => {
98
+ if (spec.type === 'ImportSpecifier') {
99
+ return spec.imported?.name === HOOK_NAME;
100
+ }
101
+ if (spec.type === 'ImportDefaultSpecifier') {
102
+ return spec.local?.name === HOOK_NAME;
103
+ }
104
+ return false;
105
+ });
106
+ if (hasHookSpecifier) {
107
+ hasHookImport = true;
108
+ }
109
+ }
110
+ },
111
+ // 检测 useScrollReveal 调用
112
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
113
+ CallExpression(node) {
114
+ if (node.callee?.name === HOOK_NAME) {
115
+ hasHookCall = true;
116
+ }
117
+ },
118
+ // 文件遍历结束时进行检查
119
+ 'Program:exit'() {
120
+ // 只有在使用了 scroll-reveal 类但未正确设置 hook 时报错
121
+ if (hasScrollRevealClass && (!hasHookImport || !hasHookCall)) {
122
+ // 在第一个使用 scroll-reveal 的节点上报错,提供更好的开发体验
123
+ const reportNode = scrollRevealNodes[0];
124
+ context.report({
125
+ node: reportNode,
126
+ ...(customMessage
127
+ ? { message: customMessage }
128
+ : { messageId: 'requireScrollRevealHook' }),
129
+ });
130
+ }
131
+ },
132
+ };
133
+ },
134
+ };
135
+ exports.default = rule;
@@ -18,6 +18,7 @@ declare const _default: {
18
18
  'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
19
19
  'require-app-container': import("eslint").Rule.RuleModule;
20
20
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
21
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
21
22
  };
22
23
  };
23
24
  };
@@ -119,32 +119,6 @@ exports.default = {
119
119
  selector: "JSXOpeningElement[name.name='a']:has(JSXAttribute[name.name='href'][value.value=/^(?!https?:|\\u002F\\u002F|mailto:|tel:|#).+/])",
120
120
  message: "Please don't use relative paths in <a> tags. Use NavLink from 'react-router-dom' instead.",
121
121
  },
122
- // 禁止 variant 为 outline|link|ghost 的 Button 使用 text-white
123
- // {
124
- // selector:
125
- // 'JSXElement[openingElement.name.name="Button"]' +
126
- // ':has(JSXAttribute[name.name="variant"][value.value=/^(outline|link|ghost)$/])' +
127
- // ':has(JSXAttribute[name.name="className"][value.value=/text-white/])',
128
- // message:
129
- // 'Button with variant="outline|link|ghost" should not use "text-white" className. This causes visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
130
- // },
131
- // // 禁止在 Button 上组合 text-primary-foreground 与 bg-background 并用的情况
132
- // {
133
- // selector:
134
- // 'JSXElement[openingElement.name.name="Button"]' +
135
- // ':has(JSXAttribute[name.name="className"][value.value=/text-primary-foreground/])' +
136
- // ':has(JSXAttribute[name.name="className"][value.value=/bg-background/])',
137
- // message:
138
- // 'Button should not use "text-primary-foreground" and "bg-background" className. This causes visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
139
- // },
140
- // // 禁止使用 text-accent
141
- // {
142
- // selector:
143
- // 'JSXAttribute[name.name="className"][value.value=/(^|\\s)text-accent(\\s|$)/]',
144
- // message:
145
- // 'Classname "text-accent" would cause visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
146
- // },
147
- // 禁止在 Tailwind 任意值语法中使用包含空格的 hsl/rgb 值
148
122
  {
149
123
  selector: 'JSXAttribute[name.name="className"][value.value=/\\[hsl\\([^\\]]*\\s[^\\]]*\\)/]',
150
124
  message: 'Tailwind 4 arbitrary values cannot contain spaces. Replace spaces with underscores in hsl() values. Example: from-[hsl(215_60%_18%)] instead of from-[hsl(215 60% 18%)]',
@@ -21,6 +21,7 @@ export declare const eslintPresets: {
21
21
  'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
22
22
  'require-app-container': import("eslint").Rule.RuleModule;
23
23
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
24
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
24
25
  };
25
26
  };
26
27
  };
@@ -20,6 +20,7 @@ declare const _default: ({
20
20
  'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
21
21
  'require-app-container': import("eslint").Rule.RuleModule;
22
22
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
23
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
23
24
  };
24
25
  };
25
26
  'react-hooks': any;
@@ -41,8 +42,8 @@ declare const _default: ({
41
42
  files: string[];
42
43
  rules: {
43
44
  'no-restricted-syntax': (string | {
44
- message: string;
45
45
  selector: string;
46
+ message: string;
46
47
  })[];
47
48
  };
48
49
  } | null)[];
@@ -5,16 +5,29 @@ const globals = require('globals');
5
5
  const reactHooks = require('eslint-plugin-react-hooks');
6
6
  const tseslint = require('typescript-eslint');
7
7
  const importPlugin = require('eslint-plugin-import');
8
+ const fs = require('fs');
9
+ const path = require('path');
8
10
  const custom_eslint_rules_1 = require("../../../custom-eslint-rules");
9
11
  // 检查是否启用宽松 lint 模式
10
12
  const isLooseMode = process.env.FORCE_FRAMEWORK_LINT_LOOSE_MODE === 'true';
13
+ // 读取项目 package.json 中的 flags 配置
14
+ function getProjectFlags() {
15
+ try {
16
+ const pkgPath = path.resolve(process.cwd(), 'package.json');
17
+ if (fs.existsSync(pkgPath)) {
18
+ const pkg = JSON.parse(fs.readFileSync(pkgPath, 'utf-8'));
19
+ return pkg.flags || {};
20
+ }
21
+ }
22
+ catch {
23
+ // ignore errors
24
+ }
25
+ return {};
26
+ }
27
+ const projectFlags = getProjectFlags();
28
+ const supportScrollReveal = projectFlags.supportScrollReveal === true;
11
29
  // 基础语法规则:所有模式都启用(包括 loose 模式)
12
30
  const baseSyntaxRules = [
13
- // SelectItem组件的value属性值不能为空字符串
14
- {
15
- message: 'The `value` attribute of the `SelectItem` component cannot be an empty string.',
16
- selector: 'JSXOpeningElement[name.name="SelectItem"]:has(JSXAttribute[name.name="value"][value.value=""]), JSXOpeningElement[name.name="SelectItem"]:has(JSXAttribute[name.name="value"][value.expression.value=""])',
17
- },
18
31
  // 禁用`window.location.href`赋值使用,可以读取
19
32
  {
20
33
  selector: 'AssignmentExpression[left.object.object.name="window"][left.object.property.name="location"][left.property.name="href"]',
@@ -83,35 +96,20 @@ const strictSyntaxRules = [
83
96
  message: "Please don't use confirm. It may conflict with window.confirm BOM method, use `Dialog` component instead for better user experience and consistency",
84
97
  selector: "CallExpression[callee.name='confirm']",
85
98
  },
86
- // 禁止 variant 为 outline|link|ghost 的 Button 使用 text-white
87
- {
88
- selector: 'JSXElement[openingElement.name.name="Button"]' +
89
- ':has(JSXAttribute[name.name="variant"][value.value=/^(outline|link|ghost)$/])' +
90
- ':has(JSXAttribute[name.name="className"][value.value=/text-white/])',
91
- message: 'Button with variant="outline|link|ghost" should not use "text-white" className. This causes visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
92
- },
93
- // 禁止在 Button 上组合 text-primary-foreground 与 bg-background 并用的情况
94
- {
95
- selector: 'JSXElement[openingElement.name.name="Button"]' +
96
- ':has(JSXAttribute[name.name="className"][value.value=/text-primary-foreground/])' +
97
- ':has(JSXAttribute[name.name="className"][value.value=/bg-background/])',
98
- message: 'Button should not use "text-primary-foreground" and "bg-background" className. This causes visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
99
- },
100
- // 禁止使用 text-accent
101
- {
102
- selector: 'JSXAttribute[name.name="className"][value.value=/(^|\\s)text-accent(\\s|$)/]',
103
- message: 'Classname "text-accent" would cause visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
104
- },
105
99
  ];
106
100
  const looseSpecificPlugins = {
107
101
  '@lark-apaas': { rules: custom_eslint_rules_1.customRules },
108
102
  };
109
- // 宽松模式下覆盖的规则(关闭非关键规则)
103
+ // 宽松模式下覆盖的规则
110
104
  const looseOverrideRules = isLooseMode
111
105
  ? {
112
106
  '@typescript-eslint/no-unsafe-function-type': 'off',
113
107
  '@typescript-eslint/no-unused-expressions': 'off',
114
108
  'no-useless-escape': 'off', // 允许不必要的转义字符
109
+ // 使用 scroll-reveal 样式类时必须调用 useScrollReveal()(需 flags.supportScrollReveal 为 true)
110
+ '@lark-apaas/require-scroll-reveal-hook': supportScrollReveal
111
+ ? 'error'
112
+ : 'off',
115
113
  }
116
114
  : {};
117
115
  // 基础配置(适用于所有文件)
@@ -19,6 +19,7 @@ export declare const eslintPresets: {
19
19
  'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
20
20
  'require-app-container': import("eslint").Rule.RuleModule;
21
21
  'no-direct-capability-api': import("eslint").Rule.RuleModule;
22
+ 'require-scroll-reveal-hook': import("eslint").Rule.RuleModule;
22
23
  };
23
24
  };
24
25
  'react-hooks': any;
@@ -40,8 +41,8 @@ export declare const eslintPresets: {
40
41
  files: string[];
41
42
  rules: {
42
43
  'no-restricted-syntax': (string | {
43
- message: string;
44
44
  selector: string;
45
+ message: string;
45
46
  })[];
46
47
  };
47
48
  } | null)[] | {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-presets",
3
- "version": "1.1.8",
3
+ "version": "1.1.10",
4
4
  "files": [
5
5
  "lib"
6
6
  ],