eslint-plugin-light 1.0.4 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,3 +1,64 @@
1
+ ## <small>1.0.12 (2025-05-30)</small>
2
+
3
+ * feat(eslint-plugin-light): add css-per-line rule ([bc1d247](https://github.com/novlan1/plugin-light/commits/bc1d247))
4
+
5
+
6
+
7
+ ## <small>1.0.11 (2025-05-28)</small>
8
+
9
+ * feat(eslint-plugin-light): 优化规则 ([9bb3a23](https://github.com/novlan1/plugin-light/commits/9bb3a23))
10
+
11
+
12
+
13
+ ## <small>1.0.10 (2025-05-28)</small>
14
+
15
+ * feat(eslint-plugin-light): 优化rule,使用minimatch ([f0a16ac](https://github.com/novlan1/plugin-light/commits/f0a16ac))
16
+
17
+
18
+
19
+ ## <small>1.0.9 (2025-05-27)</small>
20
+
21
+ * feat(plugin-light): 支持数组排除 ([db61229](https://github.com/novlan1/plugin-light/commits/db61229))
22
+
23
+
24
+
25
+ ## <small>1.0.8 (2025-05-27)</small>
26
+
27
+ * feat(plugin-light): add valid-spelling rule ([1b22947](https://github.com/novlan1/plugin-light/commits/1b22947))
28
+
29
+
30
+
31
+ ## <small>1.0.7 (2025-05-20)</small>
32
+
33
+ * feat(eslint-plugin-light): add no-multiple-script rule ([6f8e4a3](https://github.com/novlan1/plugin-light/commits/6f8e4a3))
34
+ * docs: update docs ([dc32fc4](https://github.com/novlan1/plugin-light/commits/dc32fc4))
35
+ * docs: update docs ([0c29ec7](https://github.com/novlan1/plugin-light/commits/0c29ec7))
36
+ * docs: update docs ([54c45b3](https://github.com/novlan1/plugin-light/commits/54c45b3))
37
+ * docs: update docs ([c94056a](https://github.com/novlan1/plugin-light/commits/c94056a))
38
+ * docs: update docs ([bbeeecd](https://github.com/novlan1/plugin-light/commits/bbeeecd))
39
+
40
+
41
+
42
+ ## <small>1.0.6 (2025-05-19)</small>
43
+
44
+ * feat(eslint): add no-import-all-in-one-api to eslint-config ([28b94d7](https://github.com/novlan1/plugin-light/commits/28b94d7))
45
+ * feat(eslint): add no-js-file/valid-file-name rules ([6891dea](https://github.com/novlan1/plugin-light/commits/6891dea))
46
+ * docs: update docs ([ca8d037](https://github.com/novlan1/plugin-light/commits/ca8d037))
47
+ * docs: update docs ([372bd45](https://github.com/novlan1/plugin-light/commits/372bd45))
48
+ * docs: update docs ([cb14ac8](https://github.com/novlan1/plugin-light/commits/cb14ac8))
49
+ * docs: update docs ([765487b](https://github.com/novlan1/plugin-light/commits/765487b))
50
+ * docs: update docs ([de4392a](https://github.com/novlan1/plugin-light/commits/de4392a))
51
+ * docs: update docs ([5119883](https://github.com/novlan1/plugin-light/commits/5119883))
52
+
53
+
54
+
55
+ ## <small>1.0.5 (2025-05-16)</small>
56
+
57
+ * feat(eslint-plugin-light): add no-import-all-in-one-api rule ([f92123e](https://github.com/novlan1/plugin-light/commits/f92123e))
58
+ * docs: update docs ([0721738](https://github.com/novlan1/plugin-light/commits/0721738))
59
+
60
+
61
+
1
62
  ## <small>1.0.4 (2025-04-18)</small>
2
63
 
3
64
  * chore: update license ([5ba3e91](https://github.com/novlan1/plugin-light/commits/5ba3e91))
package/README.md CHANGED
@@ -1,5 +1,4 @@
1
- ## ESLint Plugin Light
2
-
1
+ # ESLint Plugin Light
3
2
 
4
3
  <p align="center">
5
4
  <img src="https://img.shields.io/npm/dw/eslint-plugin-light">
@@ -10,25 +9,25 @@
10
9
  <img src="https://img.shields.io/github/created-at/novlan1/plugin-light">
11
10
  </p>
12
11
 
13
- Simple Eslint plugin.
12
+ 简单、易用的 ESLint 插件库。
14
13
 
15
- ### 1. Installation
14
+ ## 1. 安装
16
15
 
17
- You need to install [ESLint](https://eslint.org/) first.
16
+ 首先要安装 [ESLint](https://eslint.org/)
18
17
 
19
18
  ```sh
20
- npm i eslint -D
19
+ pnpm i eslint -D
21
20
  ```
22
21
 
23
- Next, install `eslint-plugin-light`.
22
+ 然后安装本插件 `eslint-plugin-light`。
24
23
 
25
24
  ```sh
26
25
  npm i eslint-plugin-light -D
27
26
  ```
28
27
 
29
- ### 2. Usage
28
+ ## 2. 使用
30
29
 
31
- Add `light` to the plugins section of your `.eslintrc` configuration file. You can omit the `eslint-plugin-` prefix:
30
+ `.eslintrc` 配置文件的 `plugins` 中增加本插件 `eslint-plugin-light`,或者省略 `eslint-plugin-` 前缀。
32
31
 
33
32
  ```json
34
33
  {
@@ -38,8 +37,7 @@ Add `light` to the plugins section of your `.eslintrc` configuration file. You c
38
37
  }
39
38
  ```
40
39
 
41
-
42
- Then configure the rules you want to use under the rules section.
40
+ 然后配置你想使用的规则。
43
41
 
44
42
  ```json
45
43
  {
@@ -49,7 +47,7 @@ Then configure the rules you want to use under the rules section.
49
47
  }
50
48
  ```
51
49
 
52
- or use extends:
50
+ 也可以使用本工具提供的扩展。
53
51
 
54
52
  ```json
55
53
  {
@@ -57,16 +55,14 @@ or use extends:
57
55
  }
58
56
  ```
59
57
 
58
+ ## 3. 规则
60
59
 
61
- ### 3. Supported Rules
62
-
63
- #### 3.1. light/valid-vue-comp-import
60
+ ### 3.1. valid-vue-comp-import
64
61
 
65
- 禁止从`js`文件中加载`Vue`组件
62
+ 禁止从 `js` 文件中加载 `Vue` 组件。
66
63
 
67
64
  比如,
68
65
 
69
-
70
66
  1. 导入地址是`js/ts`文件
71
67
 
72
68
  ```js
@@ -84,8 +80,6 @@ import SomeComp from 'src/local-component/ui/pages/user/account-manage/xxx.vue';
84
80
 
85
81
  注意上面的`xxx.vue`是从`index.js`中分析得到的原始文件路径。
86
82
 
87
-
88
-
89
83
  2. 导入一个目录,但目录存在`index.js`,这时候不管存不存在`index.vue`,`uni-app`转换都会失败
90
84
 
91
85
  ```js
@@ -118,9 +112,9 @@ import CComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-
118
112
  import DComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-new/comp/d.vue';
119
113
  ```
120
114
 
121
- #### 3.2. light/no-plus-turn-number
115
+ ### 3.2. no-plus-turn-number
122
116
 
123
- 禁止在`vue`的`template`中用`+`号转换字符串为数字
117
+ 禁止在 `vue` 的 `template` 中用 `+` 号转换字符串为数字
124
118
 
125
119
  比如:
126
120
 
@@ -130,7 +124,7 @@ import DComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-
130
124
  />
131
125
  ```
132
126
 
133
- 如果加了`--fix`,会被转化成:
127
+ 如果加了 `--fix`,会被转化成:
134
128
 
135
129
  ```html
136
130
  <ScheduleItem
@@ -138,7 +132,7 @@ import DComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-
138
132
  />
139
133
  ```
140
134
 
141
- #### 3.3 no-complex-key
135
+ ### 3.3. no-complex-key
142
136
 
143
137
  不要在`vue`模板中使用复杂的`key`。包括:
144
138
 
@@ -178,7 +172,7 @@ getData() {
178
172
  `uni-app`中,`key`重复的话,会造成挂载在组件上面的事件参数为`undefined`,从而不成功。
179
173
 
180
174
 
181
- #### 3.4 json-parse-try-catch
175
+ ### 3.4. json-parse-try-catch
182
176
 
183
177
  `JSON.parse` 应该加 `try catch`。
184
178
 
@@ -190,7 +184,6 @@ JSON.parse(JSON.stringify(abc));
190
184
 
191
185
  可以配置 `strict` 参数为 `true`,开启检查。
192
186
 
193
-
194
187
  ```js
195
188
  // .eslintrc.js
196
189
 
@@ -203,3 +196,222 @@ module.exports = {
203
196
  },
204
197
  }
205
198
  ```
199
+
200
+ ### 3.5. no-import-all-in-one-api
201
+
202
+ 使用 `src/api` 子仓库时, 不应该导入全部接口,而应该按需引入。
203
+
204
+ Bad case:
205
+
206
+ ```js
207
+ // bad
208
+ import { pubg_fateClient } from 'src/api/git.a.com/itrpcprotocol/esport/esport_cgi/pubg_fate/pubg_fate.http';
209
+ ```
210
+
211
+ Good case:
212
+
213
+ ```js
214
+ // good
215
+ import { QueryGameListHomePageClient } from 'src/api/git.a.com/itrpcprotocol/esport/esport_cgi/pubg_fate/pubg_fate/QueryGameListHomePage.http';
216
+ ```
217
+
218
+ Usage:
219
+
220
+ ```js
221
+ // .eslintrc.js
222
+
223
+ module.exports = {
224
+ plugins: [
225
+ 'light',
226
+ ],
227
+ rules: {
228
+ 'light/no-import-all-in-one-api': 2,
229
+ },
230
+ }
231
+ ```
232
+
233
+ 还可以配置 `excludes` 数组,指定排除哪些接口,不推荐使用。
234
+
235
+ ```js
236
+ // .eslintrc.js
237
+
238
+ module.exports = {
239
+ plugins: [
240
+ 'light',
241
+ ],
242
+ rules: {
243
+ 'light/no-import-all-in-one-api': [2, {
244
+ excludes: [
245
+ 'src/api/git.a.com/itrpcprotocol/esport/esport_cgi/pubg_fate/pubg_fate.http',
246
+ ]
247
+ }],
248
+ },
249
+ }
250
+ ```
251
+
252
+ 下面是一个案例,根据这个规则,对一个线上项目进行改造。仅仅改动了[几个文件的几行引入语句](https://git.a.com/pmd-mobile/match/gp-next/commit/20e0050fe10ed767dcb5d77dca76a9b51c23820e),就减少了主包 `50KB`,效果立竿见影。
253
+
254
+ 之前:
255
+
256
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_p8Sr7F4zjtry4mtR.png" width="600">
257
+
258
+ 之后:
259
+
260
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_MJ8Q5QXEyamnGZXz.png" width="600">
261
+
262
+ 改动的几个文件:
263
+
264
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_rfMPwBYtZmhXrthT.png" width="600">
265
+
266
+ ### 3.6. no-js-file
267
+
268
+ 运行时文件不允许使用 `js/jsx`,只允许 `ts/tsx`。子工程的 `config.js` 会自动排除。
269
+
270
+ Usage:
271
+
272
+ ```js
273
+ // .eslintrc.js
274
+
275
+ module.exports = {
276
+ plugins: [
277
+ 'light',
278
+ ],
279
+ rules: {
280
+ 'light/no-js-file': 2,
281
+ },
282
+ }
283
+ ```
284
+
285
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_NKtdBjwwj23s5PNj.png" width="900">
286
+
287
+ 配置选项:
288
+
289
+ | 字段 | 说明 | 默认值 |
290
+ | ------- | ----------------------- | ------------------------------------------------------------- |
291
+ | include | 检查的列表,`glob` 模式 | `['src/{project,local-component,local-logic}/**/*.{js,jsx}']` |
292
+ | exclude | 排除的列表,`glob` 模式 | `['src/project/*/config.js']` |
293
+
294
+ ### 3.7. valid-file-name
295
+
296
+ 文件命名格式只允许使用 [kebab-case](https://developer.mozilla.org/en-US/docs/Glossary/Kebab_case)。子工程的 `App.vue` 会自动排除。
297
+
298
+ Usage:
299
+
300
+ ```js
301
+ // .eslintrc.js
302
+
303
+ module.exports = {
304
+ plugins: [
305
+ 'light',
306
+ ],
307
+ rules: {
308
+ 'light/valid-file-name': 2,
309
+ },
310
+ }
311
+ ```
312
+
313
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_SMwQBipYEP6kbykt.png" width="900">
314
+
315
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_hJwzQDXxrcRyQZPd.png" width="900">
316
+
317
+ 配置选项:
318
+
319
+ | 字段 | 说明 | 默认值 |
320
+ | ------- | ----------------------- | --------------------------- |
321
+ | include | 检查的列表,`glob` 模式 | `['src/**/*']` |
322
+ | exclude | 排除的列表,`glob` 模式 | `['src/project/*/App.vue']` |
323
+
324
+ ### 3.8. no-multiple-script
325
+
326
+ 禁止 Vue 文件中存在多个 `<script>`。
327
+
328
+ Usage:
329
+
330
+ ```js
331
+ // .eslintrc.js
332
+
333
+ module.exports = {
334
+ plugins: [
335
+ 'light',
336
+ ],
337
+ rules: {
338
+ 'light/no-multiple-script': 2,
339
+ },
340
+ }
341
+ ```
342
+
343
+ <img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_w7ADssRftSYE6myr.png" width="900">
344
+
345
+
346
+ ### 3.9. valid-spelling
347
+
348
+ 校验正确的拼写,比如不能用"帐号",要使用"账号",不能使用"登陆",要使用"登录"。
349
+
350
+ Usage:
351
+
352
+ ```js
353
+ // .eslintrc.js
354
+
355
+ module.exports = {
356
+ plugins: [
357
+ 'light',
358
+ ],
359
+ rules: {
360
+ 'light/valid-spelling': 2,
361
+ },
362
+ }
363
+ ```
364
+
365
+ 配置选项:
366
+
367
+ | 字段 | 说明 | 默认值 |
368
+ | -------- | ------------------------ | -------------------------------- |
369
+ | spelling | 错误拼写和正确拼写的映射 | `{ 帐号: '账号', 登陆: '登录' }` |
370
+
371
+ ### 3.10. valid-shaohou
372
+
373
+ 校验正确的"稍候"和"稍后",稍候后面不加词,稍后后面需要加词,比如"请稍候/请稍后再试"。
374
+
375
+ Usage:
376
+
377
+ ```js
378
+ // .eslintrc.js
379
+
380
+ module.exports = {
381
+ plugins: [
382
+ 'light',
383
+ ],
384
+ rules: {
385
+ 'light/valid-shaohou': 2,
386
+ },
387
+ }
388
+ ```
389
+
390
+ ### 3.11. css-per-line
391
+
392
+ 限制 Vue 中每行只有`1`个CSS类名,可以配置阈值。
393
+
394
+ Usage:
395
+
396
+ ```js
397
+ // .eslintrc.js
398
+
399
+ module.exports = {
400
+ plugins: [
401
+ 'light',
402
+ ],
403
+ rules: {
404
+ 'light/css-per-line': 2,
405
+ },
406
+ }
407
+ ```
408
+
409
+ 配置选项:
410
+
411
+ | 字段 | 说明 | 默认值 |
412
+ | ------ | -------------------------------- | ------ |
413
+ | counts | 检查阈值,小于阈值时,可以在一行 | `3` |
414
+
415
+ ## 4. 更新日志
416
+
417
+ [点此查看](./CHANGELOG.md)
@@ -0,0 +1,95 @@
1
+ const utils = require('eslint-plugin-vue/lib/utils');
2
+
3
+
4
+ const DEFAULT_THRESHOLD = 3;
5
+
6
+
7
+ module.exports = {
8
+ meta: {
9
+ type: 'layout',
10
+ docs: {
11
+ description: 'Enforce one CSS class per line in Vue templates',
12
+ category: 'Stylistic Issues',
13
+ recommended: false,
14
+ },
15
+ fixable: 'whitespace',
16
+ schema: [
17
+ {
18
+ type: 'object',
19
+ properties: {
20
+ counts: {
21
+ type: 'number',
22
+ },
23
+ },
24
+ additionalProperties: false,
25
+ },
26
+ ], // 无配置选项
27
+ },
28
+ create(context) {
29
+ return utils.defineTemplateBodyVisitor(context, {
30
+ 'VAttribute[key.name=\'class\']'(node) {
31
+ if (!node.value || node.value.type !== 'VLiteral') return;
32
+
33
+ const options = context.options[0] || {};
34
+ const threshold = options.spelling || DEFAULT_THRESHOLD;
35
+
36
+
37
+ const sourceCode = context.getSourceCode();
38
+
39
+ const text = sourceCode.getText(node.value);
40
+ const classValue = node.value.value.trim();
41
+
42
+ // 检查是否已经是多行格式
43
+ if (isAlreadyMultiLine(text)) {
44
+ return;
45
+ }
46
+
47
+ const classNames = classValue.split(/\s+/).filter(c => c.trim());
48
+
49
+
50
+ if (classNames.length <= threshold) {
51
+ return;
52
+ }
53
+
54
+ context.report({
55
+ node,
56
+ loc: node.value.loc,
57
+ message: 'CSS classes should be one per line',
58
+ fix(fixer) {
59
+ const sourceCode = context.getSourceCode();
60
+ const [startQuote, endQuote] = getQuoteChars(sourceCode, node.value);
61
+
62
+ const indentation = ' '.repeat(node.loc.start.column);
63
+ const classLines = classNames.join(`\n${indentation} `);
64
+
65
+
66
+ // 保留原始引号和等号周围空格
67
+ const replacementText = `${startQuote}${classLines}${endQuote}`;
68
+
69
+ return fixer.replaceText(node.value, replacementText);
70
+ },
71
+ });
72
+ },
73
+ });
74
+ },
75
+ };
76
+
77
+ function getQuoteChars(sourceCode, node) {
78
+ const text = sourceCode.getText(node);
79
+ const firstChar = text[0];
80
+
81
+ // 处理无引号情况(理论上 Vue 模板中不应该出现)
82
+ if (firstChar !== '\'' && firstChar !== '"') {
83
+ return ['"', '"']; // 默认使用双引号
84
+ }
85
+
86
+ // 处理单引号或双引号
87
+ return [firstChar, firstChar];
88
+ }
89
+
90
+
91
+ function isAlreadyMultiLine(text) {
92
+ const lines = text.split('\n');
93
+ // 多于1行且不是空行
94
+ return lines.length > 1 && lines.some(line => line.trim().length > 0);
95
+ }
@@ -0,0 +1,50 @@
1
+ const CORE_REG = /src\/api\/[\w.-_/]+\/[a-z][\w-]+.http(.ts)?$/;
2
+
3
+
4
+ module.exports = {
5
+ meta: {
6
+ type: 'problem',
7
+ docs: {
8
+ description: '禁止从指定的文件中导入',
9
+ category: 'Possible Errors',
10
+ recommended: true,
11
+ },
12
+ schema: [
13
+ {
14
+ type: 'object',
15
+ properties: {
16
+ excludes: {
17
+ type: 'array',
18
+ items: {
19
+ type: 'string',
20
+ },
21
+ },
22
+ },
23
+ additionalProperties: false,
24
+ },
25
+ ],
26
+ messages: {
27
+ restrictedImport: '不允许从 {{filePath}} 导入。',
28
+ },
29
+ },
30
+ create(context) {
31
+ // const projectRoot = context.settings.projectRoot ?? process.cwd();
32
+ const options = context.options[0] || {};
33
+ const excludes = options.excludes || [];
34
+
35
+ return {
36
+ ImportDeclaration(node) {
37
+ const sourceValue = node.source.value;
38
+
39
+ if (CORE_REG.test(sourceValue)
40
+ && !excludes.includes(sourceValue)
41
+ ) {
42
+ context.report({
43
+ node,
44
+ message: `不允许从 ${sourceValue} 导入全部接口,应按需导入`,
45
+ });
46
+ }
47
+ },
48
+ };
49
+ },
50
+ };
@@ -0,0 +1,79 @@
1
+ const path = require('path');
2
+
3
+ const { checkFileBaseMinimatch } = require('t-comm');
4
+ const { minimatch } = require('minimatch');
5
+
6
+
7
+ const DEFAULT_INCLUDE = [
8
+ 'src/{project,local-component,local-logic}/**/*.{js,jsx}',
9
+ ];
10
+
11
+ const DEFAULT_EXCLUDE = [
12
+ 'src/project/*/config.js',
13
+ ];
14
+
15
+
16
+ module.exports = {
17
+ meta: {
18
+ type: 'problem',
19
+ docs: {
20
+ description: '禁止使用 JS 文件',
21
+ category: 'Possible Errors',
22
+ recommended: true,
23
+ },
24
+ schema: [
25
+ {
26
+ type: 'object',
27
+ properties: {
28
+ include: {
29
+ type: 'array',
30
+ items: {
31
+ type: 'string',
32
+ },
33
+ },
34
+ exclude: {
35
+ type: 'array',
36
+ items: {
37
+ type: 'string',
38
+ },
39
+ },
40
+ },
41
+ additionalProperties: false,
42
+ },
43
+ ], // 无配置选项
44
+ messages: {
45
+ },
46
+ },
47
+ create(context) {
48
+ const projectRoot = context.settings.projectRoot ?? process.cwd();
49
+ const options = context.options[0] || {};
50
+ const include = options.include || DEFAULT_INCLUDE;
51
+ const exclude = options.exclude || DEFAULT_EXCLUDE;
52
+
53
+ const filename = context.getFilename();
54
+ const pureFilePath = path.relative(projectRoot, filename);
55
+
56
+ if (!pureFilePath) {
57
+ return {};
58
+ }
59
+
60
+
61
+ const isError = checkFileBaseMinimatch({
62
+ file: pureFilePath,
63
+ include,
64
+ exclude,
65
+ minimatch,
66
+ }) && /\.(js|jsx)$/.test(pureFilePath);
67
+
68
+ if (isError) {
69
+ context.report({
70
+ node: null, // 不关联到特定 AST 节点
71
+ loc: { line: 1, column: 0 }, // 报告位置可自定义
72
+ message: '不允许使用 js/jsx,请使用 .ts 或 .tsx',
73
+ });
74
+ }
75
+
76
+
77
+ return {};
78
+ },
79
+ };
@@ -0,0 +1,33 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'problem',
4
+ docs: {
5
+ description: '禁止 Vue 文件中存在多个 <script>',
6
+ category: 'Possible Errors',
7
+ recommended: true,
8
+ },
9
+ },
10
+ create(context) {
11
+ return {
12
+ Program(node) {
13
+ const sourceCode = context.getSourceCode();
14
+ const vueFileContent = sourceCode.getText();
15
+ const filename = context.getFilename();
16
+
17
+ if (!filename.endsWith('.vue')) {
18
+ return {};
19
+ }
20
+
21
+ const scriptTagRegex = /<script\b[^>]*>([\s\S]*?)<\/script>/g;
22
+ const scriptTags = [...vueFileContent.matchAll(scriptTagRegex)];
23
+
24
+ if (scriptTags.length > 1) {
25
+ context.report({
26
+ node,
27
+ message: 'Vue 文件只能包含一个 <script> 标签。',
28
+ });
29
+ }
30
+ },
31
+ };
32
+ },
33
+ };
@@ -0,0 +1,112 @@
1
+ const path = require('path');
2
+
3
+ const { checkFileBaseMinimatch } = require('t-comm');
4
+ const { minimatch } = require('minimatch');
5
+
6
+ const DEFAULT_INCLUDE = [
7
+ 'src/**/*',
8
+ ];
9
+
10
+ const DEFAULT_EXCLUDE = [
11
+ 'src/project/*/App.vue',
12
+ ];
13
+
14
+ const KEBAB_CASE_REG = /^[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
15
+
16
+
17
+ function checkInvalidEachDirName(filePath = '', reg) {
18
+ const list = filePath.split(path.sep);
19
+ const fileName = list[list.length - 1];
20
+ const baseName = path.basename(fileName, path.extname(fileName));
21
+ if (!reg.test(baseName)) {
22
+ return {
23
+ fileName: baseName,
24
+ };
25
+ }
26
+
27
+ const errorDirs = list.slice(0, list.length - 1).filter(item => !reg.test(item));
28
+
29
+ if (errorDirs.length) {
30
+ return {
31
+ errorDirs,
32
+ };
33
+ }
34
+
35
+ return 0;
36
+ }
37
+
38
+
39
+ module.exports = {
40
+ meta: {
41
+ type: 'suggestion',
42
+ docs: {
43
+ description: 'Enforce directory names and filename to be in kebab-case',
44
+ category: 'Best Practices',
45
+ recommended: false,
46
+ },
47
+ schema: [
48
+ {
49
+ type: 'object',
50
+ properties: {
51
+ include: {
52
+ type: 'array',
53
+ items: {
54
+ type: 'string',
55
+ },
56
+ },
57
+ exclude: {
58
+ type: 'array',
59
+ items: {
60
+ type: 'string',
61
+ },
62
+ },
63
+ },
64
+ additionalProperties: false,
65
+ },
66
+ ], // 无配置选项
67
+ messages: {
68
+ },
69
+ },
70
+ create(context) {
71
+ const projectRoot = context.settings.projectRoot ?? process.cwd();
72
+ const options = context.options[0] || {};
73
+ const include = options.include || DEFAULT_INCLUDE;
74
+ const exclude = options.exclude || DEFAULT_EXCLUDE;
75
+
76
+
77
+ const filename = context.getFilename();
78
+ const pureFilePath = path.relative(projectRoot, filename);
79
+
80
+
81
+ if (!pureFilePath) {
82
+ return {};
83
+ }
84
+
85
+
86
+ const toCheck = checkFileBaseMinimatch({
87
+ file: pureFilePath,
88
+ include,
89
+ exclude,
90
+ minimatch,
91
+ });
92
+
93
+ if (!toCheck) {
94
+ return {};
95
+ }
96
+ const errorInfo = checkInvalidEachDirName(pureFilePath, KEBAB_CASE_REG);
97
+
98
+ if (errorInfo) {
99
+ const { fileName, errorDirs } = errorInfo;
100
+
101
+ context.report({
102
+ node: null,
103
+ loc: { line: 1, column: 0 },
104
+ message: fileName
105
+ ? `File name ${fileName} should be in kebab-case (e.g., 'my-file').`
106
+ : `Directory name ${errorDirs.join(',')} should be in kebab-case (e.g., 'my-directory').`,
107
+ });
108
+ }
109
+
110
+ return {};
111
+ },
112
+ };
@@ -0,0 +1,63 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ docs: {
5
+ description: '规范\'稍后\'和\'稍候\'的使用:\'稍后\'必须加词(如\'稍后再试\'),\'稍候\'不能加词(如\'请稍候\')',
6
+ category: 'Stylistic Issues',
7
+ recommended: false,
8
+ },
9
+ fixable: 'code',
10
+ schema: [], // 无选项
11
+ messages: {
12
+ laterWordRequireMore: '\'稍后\'后面必须加词,如\'稍后再试\'',
13
+ laterWordNoMore: '\'稍候\'后面不能加词,如\'请稍候\'',
14
+ },
15
+ },
16
+
17
+ create(context) {
18
+ // 检查"稍后"后面是否有跟随词语
19
+ function checkLaterWord(text, node) {
20
+ const laterWordRegex = /稍后([,。.!?、;:'"\s]|$)/;
21
+ if (laterWordRegex.test(text)) {
22
+ context.report({
23
+ node,
24
+ messageId: 'laterWordRequireMore',
25
+ fix() {
26
+ return null;
27
+ },
28
+ });
29
+ }
30
+ }
31
+
32
+ // 检查"稍候"后面是否有跟随词语
33
+ function checkLaterWord2(text, node) {
34
+ const laterWord2Regex = /稍候[^,。.!?、;:'"\s]/;
35
+ if (laterWord2Regex.test(text)) {
36
+ context.report({
37
+ node,
38
+ messageId: 'laterWordNoMore',
39
+ fix() {
40
+ return null;
41
+ },
42
+ });
43
+ }
44
+ }
45
+
46
+ return {
47
+ Literal(node) {
48
+ if (typeof node.value === 'string') {
49
+ checkLaterWord(node.value, node);
50
+ checkLaterWord2(node.value, node);
51
+ }
52
+ },
53
+ TemplateElement(node) {
54
+ checkLaterWord(node.value.raw, node);
55
+ checkLaterWord2(node.value.raw, node);
56
+ },
57
+ JSXText(node) {
58
+ checkLaterWord(node.value, node);
59
+ checkLaterWord2(node.value, node);
60
+ },
61
+ };
62
+ },
63
+ };
@@ -0,0 +1,107 @@
1
+ module.exports = {
2
+ meta: {
3
+ type: 'suggestion',
4
+ docs: {
5
+ description: '使用正确的拼写',
6
+ category: 'Stylistic Issues',
7
+ recommended: false,
8
+ },
9
+ fixable: 'code',
10
+ schema: [
11
+ {
12
+ type: 'object',
13
+ properties: {
14
+ spelling: {
15
+ type: 'object',
16
+ },
17
+ },
18
+ additionalProperties: false,
19
+ },
20
+ ],
21
+ messages: {
22
+ invalidWord: '应使用\'{{correct}}\'而非\'{{wrong}}\'',
23
+ },
24
+ },
25
+
26
+ create(context) {
27
+ const options = context.options[0] || {};
28
+ const spelling = options.spelling || {
29
+ 帐号: '账号',
30
+ 登陆: '登录',
31
+ };
32
+ const wrongList = Object.keys(spelling);
33
+
34
+ const checkIsWrong = (list, value) => {
35
+ if (typeof value !== 'string') {
36
+ return '';
37
+ }
38
+ return list.find(item => value.includes(item));
39
+ };
40
+ const fixWrongWord = ({ value, wrong, correct }) => {
41
+ const newText = value.replace(new RegExp(wrong, 'g'), correct);
42
+ return newText;
43
+ };
44
+
45
+ return {
46
+ Literal(node) {
47
+ const wrongWord = checkIsWrong(wrongList, node.value);
48
+
49
+ if (wrongWord) {
50
+ context.report({
51
+ node,
52
+ messageId: 'invalidWord',
53
+ data: {
54
+ wrong: wrongWord,
55
+ correct: spelling[wrongWord],
56
+ },
57
+ fix(fixer) {
58
+ const newText = fixWrongWord({
59
+ value: node.raw,
60
+ wrong: wrongWord,
61
+ correct: spelling[wrongWord],
62
+ });
63
+ return fixer.replaceText(node, newText);
64
+ },
65
+ });
66
+ }
67
+ },
68
+ TemplateElement(node) {
69
+ const wrongWord = checkIsWrong(wrongList, node.value.raw);
70
+ if (wrongWord) {
71
+ context.report({
72
+ node,
73
+ messageId: 'invalidWord',
74
+ data: {
75
+ wrong: wrongWord,
76
+ correct: spelling[wrongWord],
77
+ },
78
+ fix(fixer) {
79
+ const newText = fixWrongWord({
80
+ value: node.raw,
81
+ wrong: wrongWord,
82
+ correct: spelling[wrongWord],
83
+ });
84
+ return fixer.replaceTextRange(
85
+ [node.range[0] + 1, node.range[1] - 1],
86
+ newText,
87
+ );
88
+ },
89
+ });
90
+ }
91
+ },
92
+ Identifier(node) {
93
+ const wrongWord = checkIsWrong(wrongList, node.name);
94
+ if (wrongWord) {
95
+ context.report({
96
+ node,
97
+ messageId: 'invalidWord',
98
+ data: {
99
+ wrong: wrongWord,
100
+ correct: spelling[wrongWord],
101
+ },
102
+ });
103
+ }
104
+ },
105
+ };
106
+ },
107
+ };
@@ -65,20 +65,16 @@ function getDefaultExportComp(ast) {
65
65
  const namedDeclarationNodes = ast.body.filter(item => item.type === 'ExportNamedDeclaration');
66
66
 
67
67
  const namedObj = {};
68
- // console.log('namedDeclarationNodes',namedDeclarationNodes.length)
69
68
  for (let i = 0;i < namedDeclarationNodes.length;i++) {
70
69
  const node = namedDeclarationNodes[i];
71
- // console.log('node',node)
72
70
  if (!node.specifiers || !node.specifiers.length) {
73
71
  continue;
74
72
  }
75
- // console.log('i',node)
76
73
  const { value } = node.source;
77
74
  const { name } = node.specifiers[0].exported;
78
75
  namedObj[name] = value;
79
76
  }
80
77
 
81
- // console.log('namedObj', namedObj);
82
78
 
83
79
  if (namedObj.default) {
84
80
  return namedObj;
@@ -89,7 +85,6 @@ function getDefaultExportComp(ast) {
89
85
  // 是一个变量,代表从上面导入的
90
86
  const { name } = nodes[0].declaration;
91
87
  const importAst = ast.body.filter(item => item.type === 'ImportDeclaration' && !!item.specifiers.find(it => it.local.name === name));
92
- // console.log('importAst', importAst)
93
88
  namedObj.default = importAst[0].source.value;
94
89
  }
95
90
 
@@ -102,10 +97,8 @@ function getRelativePath(originPath = '') {
102
97
 
103
98
  function getDefaultExportPathFromSouceFile(sourceFile) {
104
99
  const scriptAST = getSourceAST(sourceFile);
105
- // console.log('scriptAST', scriptAST)
106
100
 
107
101
  const defaultExportComp = getDefaultExportComp(scriptAST);
108
- // console.log('defaultExportComp',defaultExportComp)
109
102
  if (!defaultExportComp) return;
110
103
 
111
104
  const obj = {};
@@ -117,9 +110,6 @@ function getDefaultExportPathFromSouceFile(sourceFile) {
117
110
  const relativePath = getRelativePath(defaultFilePath);
118
111
  obj[name] = relativePath;
119
112
  }
120
- // console.log('defaultFilePath',defaultFilePath)
121
-
122
- // console.log('relativePath',relativePath)
123
113
  return obj;
124
114
  }
125
115
 
@@ -140,14 +130,12 @@ function handleError({
140
130
  for (let i = 0;i < specifiers.length;i++) {
141
131
  const specifier = specifiers[i];
142
132
  const componentName = specifier.local.name;
143
- // console.log('componentName', componentName);
144
133
  componentList.push(componentName);
145
134
  if (relativePath[componentName]) {
146
135
  statementString += `import ${componentName} from '${relativePath[componentName]}';\n`;
147
136
  }
148
137
  }
149
138
 
150
- // console.log('relativePath', relativePath);
151
139
  context.report({
152
140
  node: one,
153
141
  messageId: isJSError ? 'jsError' : 'tsError',
@@ -194,10 +182,7 @@ module.exports = {
194
182
  // const cwd = context.getCwd();
195
183
  const fileName = context.getFilename();
196
184
 
197
- // console.log('cwd', cwd)
198
- // console.log('fileName', fileName)
199
185
  const dirname = path.dirname(fileName);
200
- // console.log('dirname', dirname);
201
186
  if (!fileName.endsWith('.vue')) {
202
187
  return {};
203
188
  }
@@ -210,14 +195,12 @@ module.exports = {
210
195
  const properties = exportAst.declaration.properties || [];
211
196
  const componentsNode = properties.find(item => item.key && item.key.name === 'components');
212
197
 
213
- // console.log('componentsNode',componentsNode)
214
198
  if (!componentsNode || !componentsNode.value
215
199
  || !componentsNode.value.properties
216
200
  || !componentsNode.value.properties.length
217
201
  ) return;
218
202
  const components = componentsNode.value.properties.map(item => ((item.value && item.value.name) || ''));
219
203
 
220
- // console.log('components', components);
221
204
 
222
205
  if (!components || !components.length) return;
223
206
 
@@ -226,7 +209,6 @@ module.exports = {
226
209
  const name = (it.imported && it.imported.name) || (it.local && it.local.name);
227
210
  return components.includes(name);
228
211
  }));
229
- // console.log('realImport',realImport, realImport.length)
230
212
 
231
213
  for (let i = 0;i < realImport.length;i++) {
232
214
  const one = realImport[i];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "eslint-plugin-light",
3
- "version": "1.0.4",
3
+ "version": "1.0.12",
4
4
  "description": "Simple Eslint Plugin",
5
5
  "keywords": [
6
6
  "eslint",
@@ -25,7 +25,9 @@
25
25
  "CHANGELOG.md"
26
26
  ],
27
27
  "dependencies": {
28
- "requireindex": "^1.2.0"
28
+ "minimatch": "^10.0.1",
29
+ "requireindex": "^1.2.0",
30
+ "t-comm": "^1.5.40"
29
31
  },
30
32
  "devDependencies": {
31
33
  "@babel/core": "^7.18.9",