eslint-plugin-light 1.0.19 → 1.0.21

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/CHANGELOG.md CHANGED
@@ -1,3 +1,15 @@
1
+ ## <small>1.0.21 (2026-01-04)</small>
2
+
3
+ * feat(eslint-plugin-light): add no-src-imports-in-components rule ([b960951](https://github.com/novlan1/plugin-light/commits/b960951))
4
+
5
+
6
+
7
+ ## <small>1.0.20 (2025-12-18)</small>
8
+
9
+ * feat(project-config-pixui): add try-catch for version-info ([775fb7b](https://github.com/novlan1/plugin-light/commits/775fb7b))
10
+
11
+
12
+
1
13
  ## <small>1.0.19 (2025-12-12)</small>
2
14
 
3
15
  * chore: update docs ([dc64df7](https://github.com/novlan1/plugin-light/commits/dc64df7))
package/README.md CHANGED
@@ -592,6 +592,215 @@ module.exports = {
592
592
  | ------ | -------- | ----------------------------------- |
593
593
  | regexp | 匹配正则 | `/\[[^\](]*\.\d+[a-zA-Z]+[^\]]*\]/` |
594
594
 
595
+ ### 4.17. valid-pmd-import
596
+
597
+ 禁止在 `packages/xx` 目录下引入对应的 `pmd-xx` 包,需要使用相对路径。
598
+
599
+ 这个规则主要用于 monorepo 项目中,防止包内部引入自己的发布版本,而是使用相对路径引入源码,避免循环依赖和版本不一致问题。
600
+
601
+ 比如,在 `packages/components` 目录下:
602
+
603
+ ```js
604
+ // ❌ 错误:引入自己的包名
605
+ import { Button } from 'pmd-components';
606
+ const utils = require('pmd-components/lib/utils');
607
+
608
+ // ✅ 正确:使用相对路径
609
+ import { Button } from '../src';
610
+ const utils = require('../../src/utils');
611
+ ```
612
+
613
+ 如果加了 `--fix`,会被自动转换为相对路径。
614
+
615
+ Usage:
616
+
617
+ ```js
618
+ // .eslintrc.js
619
+
620
+ module.exports = {
621
+ plugins: [
622
+ 'light',
623
+ ],
624
+ rules: {
625
+ 'light/valid-pmd-import': 2,
626
+ },
627
+ }
628
+ ```
629
+
630
+ 配置选项:
631
+
632
+ | 字段 | 说明 | 默认值 |
633
+ | ------- | ---------------- | ------- |
634
+ | baseDir | 包源码的基础目录 | `'src'` |
635
+
636
+ 例如,如果你的包源码不在 `src` 目录:
637
+
638
+ ```js
639
+ // .eslintrc.js
640
+
641
+ module.exports = {
642
+ plugins: [
643
+ 'light',
644
+ ],
645
+ rules: {
646
+ 'light/valid-pmd-import': [2, {
647
+ baseDir: 'lib'
648
+ }],
649
+ },
650
+ }
651
+ ```
652
+
653
+ 该规则会检查以下导入方式:
654
+
655
+ - ES6 `import` 语句
656
+ - CommonJS `require()` 调用
657
+ - 动态 `import()` 表达式
658
+ - `export ... from` 语句
659
+
660
+ ### 4.18. no-src-imports-in-components
661
+
662
+ #### 4.18.1. 规则说明
663
+
664
+ 禁止在指定目录下使用特定前缀的导入路径,强制使用相对路径。这个规则主要用于确保某些目录(如 `src/components`)中的代码可以被独立发布为 npm 包,不依赖项目的绝对路径导入。
665
+
666
+ #### 4.18.2. 为什么需要这个规则?
667
+
668
+ 当你的项目中某些目录(如 `src/components`)需要被发布为独立的 npm 包时,这些目录中的代码不应该使用项目特定的绝对路径(如 `src/...`),而应该使用相对路径。这样可以确保:
669
+
670
+ 1. 代码的可移植性
671
+ 2. 包的独立性
672
+ 3. 避免发布后的路径解析问题
673
+
674
+ #### 4.18.3. 配置选项
675
+
676
+ ```typescript
677
+ {
678
+ restrictedPaths: string[], // 需要限制的目录路径列表
679
+ bannedPrefixes: string[], // 禁止使用的导入路径前缀列表
680
+ message?: string // 自定义错误消息(可选)
681
+ }
682
+ ```
683
+
684
+ #### 4.18.4. 使用示例
685
+
686
+ ##### 4.18.4.1. 基本用法(使用默认配置)
687
+
688
+ ```typescript
689
+ // .eslintrc.js
690
+ module.exports = {
691
+ rules: {
692
+ 'light/no-src-imports-in-components': 'error',
693
+ },
694
+ };
695
+ ```
696
+
697
+ 默认配置:
698
+
699
+ - `restrictedPaths`: `['src/components']`
700
+ - `bannedPrefixes`: `['src']`
701
+
702
+ ##### 4.18.4.2. 自定义配置
703
+
704
+ ```typescript
705
+ // .eslintrc.js
706
+ module.exports = {
707
+ rules: {
708
+ 'light/no-src-imports-in-components': ['error', {
709
+ // 指定多个需要限制的目录
710
+ restrictedPaths: ['src/components', 'src/lib', 'packages/ui'],
711
+
712
+ // 指定多个禁止的导入前缀
713
+ bannedPrefixes: ['src', '@/', '~/'],
714
+
715
+ // 可选:自定义错误消息
716
+ message: '该目录将被发布为独立包,请使用相对路径导入',
717
+ }],
718
+ },
719
+ };
720
+ ```
721
+
722
+ ##### 4.18.4.3. 只检查特定目录
723
+
724
+ ```typescript
725
+ // .eslintrc.js
726
+ module.exports = {
727
+ rules: {
728
+ 'light/no-src-imports-in-components': ['error', {
729
+ restrictedPaths: ['src/components'],
730
+ bannedPrefixes: ['src'],
731
+ }],
732
+ },
733
+ };
734
+ ```
735
+
736
+ #### 4.18.5. 错误示例
737
+
738
+ 假设配置为:
739
+
740
+ ```typescript
741
+ {
742
+ restrictedPaths: ['src/components'],
743
+ bannedPrefixes: ['src']
744
+ }
745
+ ```
746
+
747
+ 在 `src/components/Button/Button.tsx` 中:
748
+
749
+ ```typescript
750
+ // ❌ 错误:使用了 src 开头的导入
751
+ import { back } from 'src/app/route/route';
752
+ import { utils } from 'src/utils/helper';
753
+
754
+ // ✅ 正确:使用相对路径
755
+ import { back } from '../../app/route/route';
756
+ import { utils } from '../../utils/helper';
757
+
758
+ // ✅ 正确:导入 npm 包
759
+ import React from 'react';
760
+ import { Button } from 'antd';
761
+ ```
762
+
763
+ #### 4.18.6. 配置参数详解
764
+
765
+ ##### 4.18.6.1. restrictedPaths
766
+
767
+ 类型:`string[]`
768
+ 默认值:`['src/components']`
769
+
770
+ 指定需要应用此规则的目录列表。规则会检查文件路径是否包含这些目录。
771
+
772
+ 示例:
773
+
774
+ ```typescript
775
+ restrictedPaths: ['src/components', 'src/lib', 'packages/ui']
776
+ ```
777
+
778
+ ##### 4.18.6.2. bannedPrefixes
779
+
780
+ 类型:`string[]`
781
+ 默认值:`['src']`
782
+
783
+ 指定禁止使用的导入路径前缀列表。任何以这些前缀开头的导入都会被标记为错误。
784
+
785
+ 示例:
786
+
787
+ ```typescript
788
+ bannedPrefixes: ['src', '@/', '~/']
789
+ ```
790
+
791
+ ##### 4.18.6.3. message
792
+
793
+ 类型:`string`
794
+ 默认值:无(使用默认错误消息)
795
+
796
+ 自定义错误消息。如果不设置,将使用默认消息模板。
797
+
798
+ 示例:
799
+
800
+ ```typescript
801
+ message: '该目录将被发布为独立的 npm 包,请使用相对路径导入'
802
+ ```
803
+
595
804
  ## 5. 更新日志
596
805
 
597
806
  [点此查看](./CHANGELOG.md)
@@ -0,0 +1,101 @@
1
+ 'use strict';
2
+
3
+ module.exports = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: '禁止在指定目录下使用特定前缀的导入路径,必须使用相对路径',
8
+ category: 'Best Practices',
9
+ recommended: true,
10
+ },
11
+ fixable: null,
12
+ schema: [
13
+ {
14
+ type: 'object',
15
+ properties: {
16
+ restrictedPaths: {
17
+ type: 'array',
18
+ description: '需要限制的目录路径列表',
19
+ items: {
20
+ type: 'string',
21
+ },
22
+ default: ['src/components'],
23
+ },
24
+ bannedPrefixes: {
25
+ type: 'array',
26
+ description: '禁止使用的导入路径前缀列表',
27
+ items: {
28
+ type: 'string',
29
+ },
30
+ default: ['src', 'src/'],
31
+ },
32
+ message: {
33
+ type: 'string',
34
+ description: '自定义错误消息',
35
+ },
36
+ },
37
+ additionalProperties: false,
38
+ },
39
+ ],
40
+ messages: {
41
+ noSrcImport: '在 {{directory}} 目录下不允许使用 "{{prefix}}" 开头的导入路径,请使用相对路径。该目录会被发布为独立的 npm 包。',
42
+ customMessage: '{{message}}',
43
+ },
44
+ },
45
+
46
+ create(context) {
47
+ // 获取配置选项
48
+ const options = context.options[0] || {};
49
+ const restrictedPaths = options.restrictedPaths || ['src/components'];
50
+ const bannedPrefixes = options.bannedPrefixes || ['src'];
51
+ const customMessage = options.message;
52
+
53
+ return {
54
+ ImportDeclaration(node) {
55
+ // 获取当前文件的绝对路径
56
+ const filename = context.getFilename();
57
+
58
+ // 标准化路径(统一使用正斜杠)
59
+ const normalizedPath = filename.replace(/\\/g, '/');
60
+
61
+ // 检查文件是否在任何受限目录下
62
+ const matchedDirectory = restrictedPaths.find((restrictedPath) => {
63
+ const normalizedRestrictedPath = restrictedPath.replace(/\\/g, '/');
64
+ return normalizedPath.includes(`/${normalizedRestrictedPath}/`)
65
+ || normalizedPath.includes(`/${normalizedRestrictedPath}`);
66
+ });
67
+
68
+ if (!matchedDirectory) {
69
+ return;
70
+ }
71
+
72
+ // 获取导入路径
73
+ const importPath = node.source.value;
74
+
75
+ // 检查是否使用了任何被禁止的前缀
76
+ const matchedPrefix = bannedPrefixes.find(prefix => importPath === prefix || importPath.startsWith(`${prefix}/`));
77
+
78
+ if (matchedPrefix) {
79
+ if (customMessage) {
80
+ context.report({
81
+ node: node.source,
82
+ messageId: 'customMessage',
83
+ data: {
84
+ message: customMessage,
85
+ },
86
+ });
87
+ } else {
88
+ context.report({
89
+ node: node.source,
90
+ messageId: 'noSrcImport',
91
+ data: {
92
+ directory: matchedDirectory,
93
+ prefix: matchedPrefix,
94
+ },
95
+ });
96
+ }
97
+ }
98
+ },
99
+ };
100
+ },
101
+ };
@@ -0,0 +1,170 @@
1
+ const path = require('path');
2
+
3
+ module.exports = {
4
+ meta: {
5
+ type: 'problem',
6
+ docs: {
7
+ description: '禁止在 packages/xx 目录下引入对应的 pmd-xx 包,需要使用相对路径',
8
+ category: 'Best Practices',
9
+ recommended: false,
10
+ },
11
+ fixable: 'code',
12
+ schema: [
13
+ {
14
+ type: 'object',
15
+ properties: {
16
+ baseDir: {
17
+ type: 'string',
18
+ description: '基础目录,默认为 src',
19
+ default: 'src',
20
+ },
21
+ },
22
+ additionalProperties: false,
23
+ },
24
+ ],
25
+ },
26
+
27
+ create(context) {
28
+ const options = context.options[0] || {};
29
+ const baseDir = options.baseDir || 'src';
30
+
31
+ const filename = context.getFilename();
32
+ const absolutePath = path.resolve(filename);
33
+
34
+ // 提取当前包名
35
+ const getCurrentPackage = (filePath) => {
36
+ const normalizedPath = path.normalize(filePath);
37
+ const match = normalizedPath.match(/packages[\\/]([^\\/]+)/);
38
+ return match ? match[1] : null;
39
+ };
40
+
41
+ const currentPackage = getCurrentPackage(absolutePath);
42
+ if (!currentPackage) {
43
+ return {};
44
+ }
45
+
46
+ const pmdPackageName = `pmd-${currentPackage}`;
47
+
48
+ /**
49
+ * 获取应该使用的相对路径
50
+ */
51
+ const getRelativePath = (importPath, currentFilePath) => {
52
+ // 移除包名前缀
53
+ const subPath = importPath.replace(pmdPackageName, '').replace(/^\/lib/, '');
54
+
55
+ const currentDir = path.dirname(currentFilePath);
56
+
57
+ // 查找当前包的根目录
58
+ const parts = absolutePath.split(path.sep);
59
+ const packageIndex = parts.findIndex((part, index) => part === 'packages' && parts[index + 1] === currentPackage);
60
+
61
+ if (packageIndex === -1) {
62
+ return '.'; // 回退到当前目录
63
+ }
64
+
65
+ // 构建包的根路径
66
+ const packageRootParts = parts.slice(0, packageIndex + 2);
67
+ const packageRoot = packageRootParts.join(path.sep);
68
+
69
+ // 构建目标路径
70
+ const targetRelative = subPath ? `${baseDir}${subPath}` : baseDir;
71
+ const targetPath = path.join(packageRoot, targetRelative);
72
+
73
+ // 计算相对路径
74
+ let relativePath = path.relative(currentDir, targetPath);
75
+
76
+ // 处理特殊情况
77
+ if (relativePath === '') {
78
+ relativePath = '.';
79
+ } else if (!relativePath.startsWith('.')) {
80
+ relativePath = `./${relativePath}`;
81
+ }
82
+
83
+ // 标准化路径分隔符
84
+ relativePath = relativePath.replace(/\\/g, '/');
85
+
86
+ return relativePath;
87
+ };
88
+
89
+ /**
90
+ * 获取引号字符
91
+ */
92
+ const getQuoteChar = (node, sourceCode) => {
93
+ if (node.source) {
94
+ const text = sourceCode.getText(node.source);
95
+ return text[0]; // 第一个字符应该是引号
96
+ } if (node.arguments && node.arguments[0]) {
97
+ const text = sourceCode.getText(node.arguments[0]);
98
+ return text[0];
99
+ }
100
+ return '\''; // 默认使用单引号
101
+ };
102
+
103
+ /**
104
+ * 获取需要修复的节点
105
+ */
106
+ const getFixNode = (node) => {
107
+ if (node.source) return node.source;
108
+ if (node.arguments && node.arguments[0]) return node.arguments[0];
109
+ return node;
110
+ };
111
+
112
+ /**
113
+ * 检查并报告非法引入
114
+ */
115
+ const checkAndReport = (node, importPath) => {
116
+ if (importPath && importPath.startsWith(pmdPackageName)) {
117
+ const sourceCode = context.getSourceCode();
118
+ const fixNode = getFixNode(node);
119
+
120
+ context.report({
121
+ node: fixNode,
122
+ message: `禁止在 packages/${currentPackage} 目录下引入 ${pmdPackageName},请使用相对路径`,
123
+ data: { importPath },
124
+ fix(fixer) {
125
+ const relativePath = getRelativePath(importPath, absolutePath);
126
+ const quoteChar = getQuoteChar(node, sourceCode);
127
+ return fixer.replaceText(fixNode, `${quoteChar}${relativePath}${quoteChar}`);
128
+ },
129
+ });
130
+ }
131
+ };
132
+
133
+ return {
134
+ ImportDeclaration(node) {
135
+ if (node.source && node.source.value) {
136
+ checkAndReport(node, node.source.value);
137
+ }
138
+ },
139
+
140
+ CallExpression(node) {
141
+ if (
142
+ node.callee
143
+ && node.callee.name === 'require'
144
+ && node.arguments.length > 0
145
+ && node.arguments[0].type === 'Literal'
146
+ ) {
147
+ checkAndReport(node, node.arguments[0].value);
148
+ }
149
+ },
150
+
151
+ ImportExpression(node) {
152
+ if (node.source && node.source.type === 'Literal') {
153
+ checkAndReport(node, node.source.value);
154
+ }
155
+ },
156
+
157
+ ExportNamedDeclaration(node) {
158
+ if (node.source && node.source.value) {
159
+ checkAndReport(node, node.source.value);
160
+ }
161
+ },
162
+
163
+ ExportAllDeclaration(node) {
164
+ if (node.source && node.source.value) {
165
+ checkAndReport(node, node.source.value);
166
+ }
167
+ },
168
+ };
169
+ },
170
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-light",
3
- "version": "1.0.19",
3
+ "version": "1.0.21",
4
4
  "description": "Simple Eslint Plugin",
5
5
  "keywords": [
6
6
  "eslint",
@@ -27,7 +27,7 @@
27
27
  "dependencies": {
28
28
  "minimatch": "^10.0.1",
29
29
  "requireindex": "^1.2.0",
30
- "t-comm": "^3.0.3"
30
+ "t-comm": "^3.0.22"
31
31
  },
32
32
  "devDependencies": {
33
33
  "@babel/core": "^7.18.9",