@lark-apaas/fullstack-presets 1.1.2 → 1.1.3-beta.2

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.
package/README.md CHANGED
@@ -1,2 +1,59 @@
1
1
  # 全栈技术栈 Presets
2
- 独立拆分 simple 目录,属于全栈精简版,目前妙搭场景专用。
2
+ 独立拆分 simple 目录,属于全栈精简版,目前妙搭场景专用。
3
+
4
+ ## ESLint 自定义规则
5
+
6
+ ### `no-nested-styled-jsx`
7
+
8
+ 检测嵌套的 styled-jsx 标签,防止编译错误。https://nextjs.org/docs/messages/nested-styled-jsx-tags
9
+
10
+ 该规则镜像了 styled-jsx babel 插件中的验证逻辑:
11
+ - [babel.js#L215](https://github.com/vercel/styled-jsx/blob/d7a59379134d73afaeb98177387cd62d54d746be/src/babel.js#L215)
12
+ - [babel.js#L222](https://github.com/vercel/styled-jsx/blob/d7a59379134d73afaeb98177387cd62d54d746be/src/babel.js#L222)
13
+
14
+ #### 使用方式
15
+
16
+ 该规则已在 `eslint-client.ts` 配置中默认启用,无需额外配置:
17
+
18
+ ```typescript
19
+ import clientConfig from '@lark-apaas/fullstack-presets/recommend/eslint/eslint-client';
20
+
21
+ export default [
22
+ clientConfig,
23
+ // 其他配置...
24
+ ];
25
+ ```
26
+
27
+ 如需单独使用该规则:
28
+
29
+ ```typescript
30
+ import { customRules } from '@lark-apaas/fullstack-presets/custom-eslint-rules';
31
+
32
+ export default [
33
+ {
34
+ plugins: {
35
+ '@lark-apaas': { rules: customRules },
36
+ },
37
+ rules: {
38
+ '@lark-apaas/no-nested-styled-jsx': 'error',
39
+ },
40
+ },
41
+ ];
42
+ ```
43
+
44
+ #### 自定义错误信息
45
+
46
+ 可通过配置自定义报错信息:
47
+
48
+ ```typescript
49
+ rules: {
50
+ '@lark-apaas/no-nested-styled-jsx': ['error', {
51
+ message: '禁止嵌套 styled-jsx 标签,请将 <style jsx> 移到组件根级别'
52
+ }],
53
+ }
54
+ ```
55
+
56
+ #### 参考资料
57
+
58
+ - [Next.js 错误说明](https://nextjs.org/docs/messages/nested-styled-jsx-tags)
59
+ - [styled-jsx GitHub Issue #42](https://github.com/vercel/styled-jsx/issues/42)
@@ -0,0 +1,3 @@
1
+ export declare const customRules: {
2
+ 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
3
+ };
@@ -0,0 +1,10 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.customRules = void 0;
7
+ const no_nested_styled_jsx_1 = __importDefault(require("./no-nested-styled-jsx"));
8
+ exports.customRules = {
9
+ 'no-nested-styled-jsx': no_nested_styled_jsx_1.default,
10
+ };
@@ -0,0 +1,13 @@
1
+ /**
2
+ * ESLint rule to detect nested styled-jsx tags.
3
+ *
4
+ * This rule mirrors the validation logic in styled-jsx's babel plugin:
5
+ * @see https://github.com/vercel/styled-jsx/blob/d7a59379134d73afaeb98177387cd62d54d746be/src/babel.js#L215
6
+ * @see https://github.com/vercel/styled-jsx/blob/d7a59379134d73afaeb98177387cd62d54d746be/src/babel.js#L222
7
+ *
8
+ * Test fixture:
9
+ * @see https://github.com/vercel/styled-jsx/blob/d7a59379134d73afaeb98177387cd62d54d746be/test/fixtures/nested-style-tags.js
10
+ */
11
+ import type { Rule } from 'eslint';
12
+ declare const rule: Rule.RuleModule;
13
+ export default rule;
@@ -0,0 +1,73 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const DEFAULT_MESSAGE = 'Detected nested styled-jsx tag. Read more: https://nextjs.org/docs/messages/nested-styled-jsx-tags';
4
+ const rule = {
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: 'Disallow nested styled-jsx tags',
9
+ category: 'Possible Errors',
10
+ recommended: true,
11
+ url: 'https://nextjs.org/docs/messages/nested-styled-jsx-tags',
12
+ },
13
+ schema: [
14
+ {
15
+ type: 'object',
16
+ properties: {
17
+ message: {
18
+ type: 'string',
19
+ description: 'Custom error message',
20
+ },
21
+ },
22
+ additionalProperties: false,
23
+ },
24
+ ],
25
+ messages: {
26
+ nestedStyleJsx: DEFAULT_MESSAGE,
27
+ },
28
+ },
29
+ create(context) {
30
+ const options = context.options[0];
31
+ const customMessage = options?.message;
32
+ return {
33
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
34
+ JSXElement(node) {
35
+ const openingElement = node.openingElement;
36
+ if (!openingElement)
37
+ return;
38
+ const elementName = openingElement.name;
39
+ // Check if this is a <style> element with jsx attribute
40
+ if (elementName.type !== 'JSXIdentifier' || elementName.name !== 'style') {
41
+ return;
42
+ }
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ const hasJsxAttribute = openingElement.attributes.some((attr) => attr.type === 'JSXAttribute' &&
45
+ attr.name?.type === 'JSXIdentifier' &&
46
+ attr.name?.name === 'jsx');
47
+ if (!hasJsxAttribute) {
48
+ return;
49
+ }
50
+ // Count JSXElement ancestors
51
+ // styled-jsx uses ignoreClosing counter that increments on JSXOpeningElement enter
52
+ // and checks if ignoreClosing > 1 on style tag exit
53
+ // @see https://github.com/vercel/styled-jsx/blob/d7a59379134d73afaeb98177387cd62d54d746be/src/babel.js#L222
54
+ let jsxElementCount = 0;
55
+ let current = node.parent;
56
+ while (current) {
57
+ if (current.type === 'JSXElement') {
58
+ jsxElementCount++;
59
+ }
60
+ current = current.parent;
61
+ }
62
+ // If there's more than 1 JSXElement ancestor, the style tag is nested
63
+ if (jsxElementCount > 1) {
64
+ context.report({
65
+ node,
66
+ ...(customMessage ? { message: customMessage } : { messageId: 'nestedStyleJsx' }),
67
+ });
68
+ }
69
+ },
70
+ };
71
+ },
72
+ };
73
+ exports.default = rule;
@@ -1,3 +1,7 @@
1
+ export declare const looseRestrictSyntaxRules: {
2
+ selector: string;
3
+ message: string;
4
+ }[];
1
5
  declare const _default: {
2
6
  name: string;
3
7
  languageOptions: {
@@ -11,6 +15,11 @@ declare const _default: {
11
15
  globals: any;
12
16
  };
13
17
  plugins: {
18
+ '@lark-apaas'?: {
19
+ rules: {
20
+ 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
21
+ };
22
+ } | undefined;
14
23
  'react-hooks': any;
15
24
  import: any;
16
25
  };
@@ -1,9 +1,11 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.looseRestrictSyntaxRules = void 0;
3
4
  const globals = require('globals');
4
5
  const reactHooks = require('eslint-plugin-react-hooks');
5
6
  const tseslint = require('typescript-eslint');
6
7
  const importPlugin = require('eslint-plugin-import');
8
+ const custom_eslint_rules_1 = require("../../../custom-eslint-rules");
7
9
  // 检查是否启用宽松 lint 模式
8
10
  const isLooseMode = process.env.FORCE_FRAMEWORK_LINT_LOOSE_MODE === 'true';
9
11
  // 基础语法规则:所有模式都启用(包括 loose 模式)
@@ -29,6 +31,20 @@ const baseSyntaxRules = [
29
31
  message: "Please don't use relative paths in <a> tags. Use NavLink from 'react-router-dom' instead.",
30
32
  },
31
33
  ];
34
+ // loose模式特化的规则
35
+ exports.looseRestrictSyntaxRules = [
36
+ // 约束RoutesComponent路由组件 dom规则
37
+ {
38
+ // 箭头函数 block body
39
+ selector: "VariableDeclarator[id.name='RoutesComponent'] > ArrowFunctionExpression > BlockStatement > ReturnStatement[argument.type='JSXElement'][argument.openingElement.name.name!='Routes']",
40
+ message: 'RoutesComponent must return <Routes> directly without any wrapper',
41
+ },
42
+ {
43
+ // 箭头函数 expression body
44
+ selector: "VariableDeclarator[id.name='RoutesComponent'] > ArrowFunctionExpression > JSXElement:not([openingElement.name.name='Routes'])",
45
+ message: 'RoutesComponent must return <Routes> directly without any wrapper',
46
+ },
47
+ ];
32
48
  // 严格语法规则:仅正常模式启用,loose 模式下不启用
33
49
  const strictSyntaxRules = [
34
50
  // 限制使用 fetch(推荐使用生成的 API Client)
@@ -78,9 +94,16 @@ const strictSyntaxRules = [
78
94
  message: 'Classname "text-accent" would cause visibility issues. Consider using proper semantic color tokens from `client/src/tailwind-theme.css`',
79
95
  },
80
96
  ];
97
+ const looseSpecificPlugins = {
98
+ '@lark-apaas': { rules: custom_eslint_rules_1.customRules }
99
+ };
100
+ const looseSpecificRules = {
101
+ '@lark-apaas/no-nested-styled-jsx': 'error'
102
+ };
81
103
  // 宽松模式下覆盖的规则(关闭非关键规则)
82
104
  const looseOverrideRules = isLooseMode
83
105
  ? {
106
+ ...looseSpecificRules,
84
107
  '@typescript-eslint/no-unsafe-function-type': 'off',
85
108
  '@typescript-eslint/no-unused-expressions': 'off',
86
109
  'no-useless-escape': 'off', // 允许不必要的转义字符
@@ -104,6 +127,7 @@ exports.default = {
104
127
  plugins: {
105
128
  'react-hooks': reactHooks,
106
129
  import: importPlugin,
130
+ ...(isLooseMode ? looseSpecificPlugins : {}),
107
131
  },
108
132
  settings: {
109
133
  'import/resolver': {
@@ -149,7 +173,11 @@ exports.default = {
149
173
  ],
150
174
  },
151
175
  ],
152
- 'no-restricted-syntax': ['error', ...baseSyntaxRules, ...(isLooseMode ? [] : strictSyntaxRules)],
176
+ 'no-restricted-syntax': [
177
+ 'error',
178
+ ...baseSyntaxRules,
179
+ ...(isLooseMode ? exports.looseRestrictSyntaxRules : strictSyntaxRules),
180
+ ],
153
181
  // 宽松模式下覆盖上述规则
154
182
  ...looseOverrideRules,
155
183
  },
@@ -14,6 +14,11 @@ export declare const eslintPresets: {
14
14
  globals: any;
15
15
  };
16
16
  plugins: {
17
+ '@lark-apaas'?: {
18
+ rules: {
19
+ 'no-nested-styled-jsx': import("eslint").Rule.RuleModule;
20
+ };
21
+ } | undefined;
17
22
  'react-hooks': any;
18
23
  import: any;
19
24
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lark-apaas/fullstack-presets",
3
- "version": "1.1.2",
3
+ "version": "1.1.3-beta.2",
4
4
  "files": [
5
5
  "lib"
6
6
  ],
@@ -18,6 +18,7 @@
18
18
  "build": "tsc && npm run copy:json",
19
19
  "copy:json": "node scripts/copy-json.js",
20
20
  "watch": "tsc --watch",
21
+ "test": "vitest",
21
22
  "prepublishOnly": "npm run build"
22
23
  },
23
24
  "dependencies": {
@@ -33,8 +34,11 @@
33
34
  "tailwindcss-animate": "^1.0.7"
34
35
  },
35
36
  "devDependencies": {
37
+ "@babel/core": "^7.24.0",
38
+ "@babel/preset-react": "^7.24.0",
36
39
  "@types/eslint": "^9.6.0",
37
40
  "eslint": "^9.35.0",
41
+ "styled-jsx": "^5.1.6",
38
42
  "typescript": "^5.9.2",
39
43
  "typescript-eslint": "^8.44.0"
40
44
  },