@lark-apaas/fullstack-presets 1.1.11 → 1.1.12
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/lib/custom-stylelint-rules/hsl-variable.d.ts +34 -0
- package/lib/custom-stylelint-rules/hsl-variable.js +116 -0
- package/lib/recommend/eslint/eslint-client.js +1 -1
- package/lib/recommend/stylelint/index.d.ts +2 -0
- package/lib/recommend/stylelint/index.js +37 -0
- package/lib/simple/recommend/eslint/eslint-client.js +1 -1
- package/lib/simple/recommend/stylelint/index.d.ts +2 -0
- package/lib/simple/recommend/stylelint/index.js +37 -0
- package/package.json +6 -2
- package/lib/custom-eslint-rules/no-welcome-index-route.d.ts +0 -12
- package/lib/custom-eslint-rules/no-welcome-index-route.js +0 -123
- package/lib/custom-eslint-rules/require-index-route.d.ts +0 -14
- package/lib/custom-eslint-rules/require-index-route.js +0 -98
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 自定义 Stylelint 规则:校验 CSS 声明中非法的 hsl() 写法
|
|
3
|
+
*
|
|
4
|
+
* 遍历所有 CSS 声明(包括自定义属性 --*),当值以 hsl()/hsla() 开头时,
|
|
5
|
+
* 使用 css-tree 的词法器验证其是否符合 CSS color 规范。
|
|
6
|
+
*
|
|
7
|
+
* 注意:只处理纯颜色值声明,不处理复合值(如 box-shadow、background 等)中的 hsl()。
|
|
8
|
+
*
|
|
9
|
+
* 非法示例:
|
|
10
|
+
* --color: hsl(100%, 50%, 26%); // hue 不能是百分比
|
|
11
|
+
* --color: hsl(200 50% 26% 0.5); // alpha 缺少斜杠
|
|
12
|
+
* --color: hsl(200 50%); // 缺少 lightness
|
|
13
|
+
* color: hsl(); // 空参数
|
|
14
|
+
*
|
|
15
|
+
* 合法示例:
|
|
16
|
+
* --color: hsl(200, 50%, 26%); // CSS3 逗号写法
|
|
17
|
+
* --color: hsl(200 50% 26%); // CSS4 空格写法
|
|
18
|
+
* --color: hsl(200deg 50% 26%); // 带单位的 hue
|
|
19
|
+
* --color: hsl(200 50% 26% / 0.5); // CSS4 带 alpha
|
|
20
|
+
*
|
|
21
|
+
* 跳过(不检查)的示例:
|
|
22
|
+
* --shadow: 0px 2px 12px hsl(0 0% 0% / 0.01); // 复合值中的 hsl
|
|
23
|
+
* background: linear-gradient(hsl(...), ...); // 复合值中的 hsl
|
|
24
|
+
*/
|
|
25
|
+
import stylelint from 'stylelint';
|
|
26
|
+
export declare const ruleName = "custom/hsl-valid-value";
|
|
27
|
+
export declare const messages: {
|
|
28
|
+
invalidHsl: (value: string) => string;
|
|
29
|
+
};
|
|
30
|
+
export declare const meta: {
|
|
31
|
+
url: string;
|
|
32
|
+
};
|
|
33
|
+
declare const _default: stylelint.Plugin;
|
|
34
|
+
export default _default;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.meta = exports.messages = exports.ruleName = void 0;
|
|
40
|
+
/**
|
|
41
|
+
* 自定义 Stylelint 规则:校验 CSS 声明中非法的 hsl() 写法
|
|
42
|
+
*
|
|
43
|
+
* 遍历所有 CSS 声明(包括自定义属性 --*),当值以 hsl()/hsla() 开头时,
|
|
44
|
+
* 使用 css-tree 的词法器验证其是否符合 CSS color 规范。
|
|
45
|
+
*
|
|
46
|
+
* 注意:只处理纯颜色值声明,不处理复合值(如 box-shadow、background 等)中的 hsl()。
|
|
47
|
+
*
|
|
48
|
+
* 非法示例:
|
|
49
|
+
* --color: hsl(100%, 50%, 26%); // hue 不能是百分比
|
|
50
|
+
* --color: hsl(200 50% 26% 0.5); // alpha 缺少斜杠
|
|
51
|
+
* --color: hsl(200 50%); // 缺少 lightness
|
|
52
|
+
* color: hsl(); // 空参数
|
|
53
|
+
*
|
|
54
|
+
* 合法示例:
|
|
55
|
+
* --color: hsl(200, 50%, 26%); // CSS3 逗号写法
|
|
56
|
+
* --color: hsl(200 50% 26%); // CSS4 空格写法
|
|
57
|
+
* --color: hsl(200deg 50% 26%); // 带单位的 hue
|
|
58
|
+
* --color: hsl(200 50% 26% / 0.5); // CSS4 带 alpha
|
|
59
|
+
*
|
|
60
|
+
* 跳过(不检查)的示例:
|
|
61
|
+
* --shadow: 0px 2px 12px hsl(0 0% 0% / 0.01); // 复合值中的 hsl
|
|
62
|
+
* background: linear-gradient(hsl(...), ...); // 复合值中的 hsl
|
|
63
|
+
*/
|
|
64
|
+
const stylelint_1 = __importDefault(require("stylelint"));
|
|
65
|
+
const csstree = __importStar(require("css-tree"));
|
|
66
|
+
exports.ruleName = 'custom/hsl-valid-value';
|
|
67
|
+
const { report, ruleMessages, validateOptions } = stylelint_1.default.utils;
|
|
68
|
+
exports.messages = ruleMessages(exports.ruleName, {
|
|
69
|
+
invalidHsl: (value) => `Invalid hsl() value: "${value}". Refer to https://developer.mozilla.org/docs/Web/CSS/color_value/hsl`,
|
|
70
|
+
});
|
|
71
|
+
exports.meta = {
|
|
72
|
+
url: 'https://github.com/csstree/stylelint-validator',
|
|
73
|
+
};
|
|
74
|
+
const rule = primary => {
|
|
75
|
+
return (root, result) => {
|
|
76
|
+
const validOptions = validateOptions(result, exports.ruleName, {
|
|
77
|
+
actual: primary,
|
|
78
|
+
possible: [true],
|
|
79
|
+
});
|
|
80
|
+
if (!validOptions || !primary)
|
|
81
|
+
return;
|
|
82
|
+
root.walkDecls(decl => {
|
|
83
|
+
const value = decl.value.trim();
|
|
84
|
+
// 只处理纯 hsl/hsla 颜色值,跳过复合值(如 box-shadow)
|
|
85
|
+
if (!/^hsla?\(/i.test(value))
|
|
86
|
+
return;
|
|
87
|
+
try {
|
|
88
|
+
const ast = csstree.parse(value, { context: 'value' });
|
|
89
|
+
const matchResult = csstree.lexer.matchType('color', ast);
|
|
90
|
+
if (matchResult.error) {
|
|
91
|
+
report({
|
|
92
|
+
message: exports.messages.invalidHsl(value),
|
|
93
|
+
node: decl,
|
|
94
|
+
result,
|
|
95
|
+
ruleName: exports.ruleName,
|
|
96
|
+
word: value,
|
|
97
|
+
});
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
// css-tree parse 失败也视为非法值
|
|
102
|
+
report({
|
|
103
|
+
message: exports.messages.invalidHsl(value),
|
|
104
|
+
node: decl,
|
|
105
|
+
result,
|
|
106
|
+
ruleName: exports.ruleName,
|
|
107
|
+
word: value,
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
});
|
|
111
|
+
};
|
|
112
|
+
};
|
|
113
|
+
rule.ruleName = exports.ruleName;
|
|
114
|
+
rule.messages = exports.messages;
|
|
115
|
+
rule.meta = exports.meta;
|
|
116
|
+
exports.default = stylelint_1.default.createPlugin(exports.ruleName, rule);
|
|
@@ -59,7 +59,7 @@ exports.default = {
|
|
|
59
59
|
'no-case-declarations': 'off', // 允许在 case 块中使用词法声明
|
|
60
60
|
'no-empty': 'off', // 允许空的 catch 块
|
|
61
61
|
// Import 规则
|
|
62
|
-
'import/no-unresolved': 'error', //
|
|
62
|
+
'import/no-unresolved': ['error', { ignore: ['\\?raw$'] }], // 检查导入路径是否存在,忽略 ?raw 查询参数
|
|
63
63
|
// 其他规则
|
|
64
64
|
'no-constant-binary-expression': 'off', // 不强制使用常量二进制表达式
|
|
65
65
|
// React Refresh 相关 - 开发时不影响渲染的检测
|
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.stylelintPresets = void 0;
|
|
37
|
+
const hsl_variable_1 = __importStar(require("../../custom-stylelint-rules/hsl-variable"));
|
|
38
|
+
const isLooseMode = process.env.FORCE_FRAMEWORK_LINT_LOOSE_MODE === 'true';
|
|
4
39
|
exports.stylelintPresets = {
|
|
40
|
+
plugins: [hsl_variable_1.default],
|
|
5
41
|
rules: {
|
|
6
42
|
'declaration-block-no-duplicate-custom-properties': true,
|
|
43
|
+
[hsl_variable_1.ruleName]: isLooseMode || null,
|
|
7
44
|
},
|
|
8
45
|
};
|
|
@@ -186,7 +186,7 @@ const baseConfig = {
|
|
|
186
186
|
'no-useless-escape': 'off',
|
|
187
187
|
'no-case-declarations': 'off',
|
|
188
188
|
// Import 规则
|
|
189
|
-
'import/no-unresolved': 'error', //
|
|
189
|
+
'import/no-unresolved': ['error', { ignore: ['\\?raw$'] }], // 检查导入路径是否存在,忽略 ?raw 查询参数
|
|
190
190
|
// 其他规则
|
|
191
191
|
'no-constant-binary-expression': 'off', // 不强制使用常量二进制表达式
|
|
192
192
|
// React Refresh 相关 - 开发时不影响渲染的检测
|
|
@@ -1,8 +1,45 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.stylelintPresets = void 0;
|
|
37
|
+
const hsl_variable_1 = __importStar(require("../../../custom-stylelint-rules/hsl-variable"));
|
|
38
|
+
const isLooseMode = process.env.FORCE_FRAMEWORK_LINT_LOOSE_MODE === 'true';
|
|
4
39
|
exports.stylelintPresets = {
|
|
40
|
+
plugins: [hsl_variable_1.default],
|
|
5
41
|
rules: {
|
|
6
42
|
'declaration-block-no-duplicate-custom-properties': true,
|
|
43
|
+
[hsl_variable_1.ruleName]: isLooseMode || null,
|
|
7
44
|
},
|
|
8
45
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@lark-apaas/fullstack-presets",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.12",
|
|
4
4
|
"files": [
|
|
5
5
|
"lib"
|
|
6
6
|
],
|
|
@@ -26,21 +26,25 @@
|
|
|
26
26
|
"@eslint/js": "^9.35.0",
|
|
27
27
|
"@types/node": "^22.19.1",
|
|
28
28
|
"@types/styled-jsx": "^2.2.9",
|
|
29
|
+
"css-tree": "^3.1.0",
|
|
29
30
|
"eslint-import-resolver-alias": "^1.1.2",
|
|
30
31
|
"eslint-plugin-import": "^2.32.0",
|
|
31
32
|
"eslint-plugin-react": "^7.37.5",
|
|
32
33
|
"eslint-plugin-react-hooks": "^5.2.0",
|
|
33
34
|
"globals": "^16.4.0",
|
|
35
|
+
"stylelint": "^17.3.0",
|
|
34
36
|
"tailwindcss-animate": "^1.0.7"
|
|
35
37
|
},
|
|
36
38
|
"devDependencies": {
|
|
37
39
|
"@babel/core": "^7.24.0",
|
|
38
40
|
"@babel/preset-react": "^7.24.0",
|
|
41
|
+
"@types/css-tree": "^2.3.11",
|
|
39
42
|
"@types/eslint": "^9.6.0",
|
|
40
43
|
"eslint": "^9.35.0",
|
|
41
44
|
"styled-jsx": "^5.1.6",
|
|
42
45
|
"typescript": "^5.9.2",
|
|
43
|
-
"typescript-eslint": "^8.44.0"
|
|
46
|
+
"typescript-eslint": "^8.44.0",
|
|
47
|
+
"vitest": "^2.0.0"
|
|
44
48
|
},
|
|
45
49
|
"peerDependencies": {
|
|
46
50
|
"eslint": "^9.0.0",
|
|
@@ -1,12 +0,0 @@
|
|
|
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;
|
|
@@ -1,123 +0,0 @@
|
|
|
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;
|
|
@@ -1,14 +0,0 @@
|
|
|
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;
|
|
@@ -1,98 +0,0 @@
|
|
|
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;
|