eslint-plugin-light 1.0.5 → 1.0.14
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 +67 -0
- package/README.md +178 -10
- package/lib/rules/classname-per-line.js +98 -0
- package/lib/rules/no-js-file.js +79 -0
- package/lib/rules/no-multiple-script.js +33 -0
- package/lib/rules/valid-file-name.js +112 -0
- package/lib/rules/valid-shaohou.js +63 -0
- package/lib/rules/valid-spelling.js +107 -0
- package/lib/rules/valid-vue-comp-import.js +0 -18
- package/package.json +5 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,70 @@
|
|
|
1
|
+
## <small>1.0.14 (2025-06-11)</small>
|
|
2
|
+
|
|
3
|
+
* feat(eslint-plugin-light): 优化classname-per-line ([fa99594](https://github.com/novlan1/plugin-light/commits/fa99594))
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## <small>1.0.13 (2025-06-10)</small>
|
|
8
|
+
|
|
9
|
+
* feat(eslint-plugin-light): 优化规则名称 ([73affad](https://github.com/novlan1/plugin-light/commits/73affad))
|
|
10
|
+
* chore: update t-comm@1.5.40 ([d019355](https://github.com/novlan1/plugin-light/commits/d019355))
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
## <small>1.0.12 (2025-05-30)</small>
|
|
15
|
+
|
|
16
|
+
* feat(eslint-plugin-light): add classname-per-line rule ([bc1d247](https://github.com/novlan1/plugin-light/commits/bc1d247))
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
## <small>1.0.11 (2025-05-28)</small>
|
|
21
|
+
|
|
22
|
+
* feat(eslint-plugin-light): 优化规则 ([9bb3a23](https://github.com/novlan1/plugin-light/commits/9bb3a23))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
## <small>1.0.10 (2025-05-28)</small>
|
|
27
|
+
|
|
28
|
+
* feat(eslint-plugin-light): 优化rule,使用minimatch ([f0a16ac](https://github.com/novlan1/plugin-light/commits/f0a16ac))
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
## <small>1.0.9 (2025-05-27)</small>
|
|
33
|
+
|
|
34
|
+
* feat(plugin-light): 支持数组排除 ([db61229](https://github.com/novlan1/plugin-light/commits/db61229))
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
## <small>1.0.8 (2025-05-27)</small>
|
|
39
|
+
|
|
40
|
+
* feat(plugin-light): add valid-spelling rule ([1b22947](https://github.com/novlan1/plugin-light/commits/1b22947))
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
## <small>1.0.7 (2025-05-20)</small>
|
|
45
|
+
|
|
46
|
+
* feat(eslint-plugin-light): add no-multiple-script rule ([6f8e4a3](https://github.com/novlan1/plugin-light/commits/6f8e4a3))
|
|
47
|
+
* docs: update docs ([dc32fc4](https://github.com/novlan1/plugin-light/commits/dc32fc4))
|
|
48
|
+
* docs: update docs ([0c29ec7](https://github.com/novlan1/plugin-light/commits/0c29ec7))
|
|
49
|
+
* docs: update docs ([54c45b3](https://github.com/novlan1/plugin-light/commits/54c45b3))
|
|
50
|
+
* docs: update docs ([c94056a](https://github.com/novlan1/plugin-light/commits/c94056a))
|
|
51
|
+
* docs: update docs ([bbeeecd](https://github.com/novlan1/plugin-light/commits/bbeeecd))
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
## <small>1.0.6 (2025-05-19)</small>
|
|
56
|
+
|
|
57
|
+
* feat(eslint): add no-import-all-in-one-api to eslint-config ([28b94d7](https://github.com/novlan1/plugin-light/commits/28b94d7))
|
|
58
|
+
* feat(eslint): add no-js-file/valid-file-name rules ([6891dea](https://github.com/novlan1/plugin-light/commits/6891dea))
|
|
59
|
+
* docs: update docs ([ca8d037](https://github.com/novlan1/plugin-light/commits/ca8d037))
|
|
60
|
+
* docs: update docs ([372bd45](https://github.com/novlan1/plugin-light/commits/372bd45))
|
|
61
|
+
* docs: update docs ([cb14ac8](https://github.com/novlan1/plugin-light/commits/cb14ac8))
|
|
62
|
+
* docs: update docs ([765487b](https://github.com/novlan1/plugin-light/commits/765487b))
|
|
63
|
+
* docs: update docs ([de4392a](https://github.com/novlan1/plugin-light/commits/de4392a))
|
|
64
|
+
* docs: update docs ([5119883](https://github.com/novlan1/plugin-light/commits/5119883))
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
|
|
1
68
|
## <small>1.0.5 (2025-05-16)</small>
|
|
2
69
|
|
|
3
70
|
* feat(eslint-plugin-light): add no-import-all-in-one-api rule ([f92123e](https://github.com/novlan1/plugin-light/commits/f92123e))
|
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
# ESLint Plugin Light
|
|
2
2
|
|
|
3
3
|
<p align="center">
|
|
4
4
|
<img src="https://img.shields.io/npm/dw/eslint-plugin-light">
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
简单、易用的 ESLint 插件库。
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
## 1. 安装
|
|
15
15
|
|
|
16
16
|
首先要安装 [ESLint](https://eslint.org/)。
|
|
17
17
|
|
|
@@ -25,7 +25,7 @@ pnpm i eslint -D
|
|
|
25
25
|
npm i eslint-plugin-light -D
|
|
26
26
|
```
|
|
27
27
|
|
|
28
|
-
|
|
28
|
+
## 2. 使用
|
|
29
29
|
|
|
30
30
|
在 `.eslintrc` 配置文件的 `plugins` 中增加本插件 `eslint-plugin-light`,或者省略 `eslint-plugin-` 前缀。
|
|
31
31
|
|
|
@@ -55,9 +55,9 @@ npm i eslint-plugin-light -D
|
|
|
55
55
|
}
|
|
56
56
|
```
|
|
57
57
|
|
|
58
|
-
|
|
58
|
+
## 3. 规则
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
### 3.1. valid-vue-comp-import
|
|
61
61
|
|
|
62
62
|
禁止从 `js` 文件中加载 `Vue` 组件。
|
|
63
63
|
|
|
@@ -112,7 +112,7 @@ import CComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-
|
|
|
112
112
|
import DComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-new/comp/d.vue';
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
|
|
115
|
+
### 3.2. no-plus-turn-number
|
|
116
116
|
|
|
117
117
|
禁止在 `vue` 的 `template` 中用 `+` 号转换字符串为数字
|
|
118
118
|
|
|
@@ -132,7 +132,7 @@ import DComp from 'src/local-component/module/tip-match/tip-match-schedule-tree-
|
|
|
132
132
|
/>
|
|
133
133
|
```
|
|
134
134
|
|
|
135
|
-
|
|
135
|
+
### 3.3. no-complex-key
|
|
136
136
|
|
|
137
137
|
不要在`vue`模板中使用复杂的`key`。包括:
|
|
138
138
|
|
|
@@ -172,7 +172,7 @@ getData() {
|
|
|
172
172
|
`uni-app`中,`key`重复的话,会造成挂载在组件上面的事件参数为`undefined`,从而不成功。
|
|
173
173
|
|
|
174
174
|
|
|
175
|
-
|
|
175
|
+
### 3.4. json-parse-try-catch
|
|
176
176
|
|
|
177
177
|
`JSON.parse` 应该加 `try catch`。
|
|
178
178
|
|
|
@@ -197,7 +197,7 @@ module.exports = {
|
|
|
197
197
|
}
|
|
198
198
|
```
|
|
199
199
|
|
|
200
|
-
|
|
200
|
+
### 3.5. no-import-all-in-one-api
|
|
201
201
|
|
|
202
202
|
使用 `src/api` 子仓库时, 不应该导入全部接口,而应该按需引入。
|
|
203
203
|
|
|
@@ -230,6 +230,25 @@ module.exports = {
|
|
|
230
230
|
}
|
|
231
231
|
```
|
|
232
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
|
+
|
|
233
252
|
下面是一个案例,根据这个规则,对一个线上项目进行改造。仅仅改动了[几个文件的几行引入语句](https://git.a.com/pmd-mobile/match/gp-next/commit/20e0050fe10ed767dcb5d77dca76a9b51c23820e),就减少了主包 `50KB`,效果立竿见影。
|
|
234
253
|
|
|
235
254
|
之前:
|
|
@@ -244,6 +263,155 @@ module.exports = {
|
|
|
244
263
|
|
|
245
264
|
<img src="https://mike-1255355338.cos.ap-guangzhou.myqcloud.com/article/2025/5/own_mike_rfMPwBYtZmhXrthT.png" width="600">
|
|
246
265
|
|
|
247
|
-
###
|
|
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. classname-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/classname-per-line': 2,
|
|
405
|
+
},
|
|
406
|
+
}
|
|
407
|
+
```
|
|
408
|
+
|
|
409
|
+
配置选项:
|
|
410
|
+
|
|
411
|
+
| 字段 | 说明 | 默认值 |
|
|
412
|
+
| ------ | -------------------------------- | ------ |
|
|
413
|
+
| counts | 检查阈值,小于阈值时,可以在一行 | `3` |
|
|
414
|
+
|
|
415
|
+
## 4. 更新日志
|
|
248
416
|
|
|
249
417
|
[点此查看](./CHANGELOG.md)
|
|
@@ -0,0 +1,98 @@
|
|
|
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, threshold)) {
|
|
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, threshold) {
|
|
92
|
+
const lines = text.split('\n');
|
|
93
|
+
|
|
94
|
+
const bad = lines.find(line => line.split(/\s+/).length > threshold);
|
|
95
|
+
|
|
96
|
+
// 多于1行且不是空行
|
|
97
|
+
return lines.length > 1 && lines.some(line => line.trim().length > 0) && !bad;
|
|
98
|
+
}
|
|
@@ -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.
|
|
3
|
+
"version": "1.0.14",
|
|
4
4
|
"description": "Simple Eslint Plugin",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"eslint",
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"eslint-plugin",
|
|
9
9
|
"eslint-plugin-light"
|
|
10
10
|
],
|
|
11
|
-
"homepage": "https://novlan1.github.io/plugin-light/zh/eslint-plugin-light.html",
|
|
11
|
+
"homepage": "https://novlan1.github.io/docs/plugin-light/zh/eslint-plugin-light.html",
|
|
12
12
|
"bugs": {
|
|
13
13
|
"url": "https://github.com/novlan1/plugin-light/issues"
|
|
14
14
|
},
|
|
@@ -25,7 +25,9 @@
|
|
|
25
25
|
"CHANGELOG.md"
|
|
26
26
|
],
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"
|
|
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",
|