eslint-plugin-light 1.0.14 → 1.0.17
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 +18 -0
- package/README.md +76 -0
- package/lib/rules/img-v-lazy.js +56 -0
- package/lib/rules/no-direct-img-src.js +43 -0
- package/lib/rules/no-todo-comment.js +42 -0
- package/lib/test/no-direct-img-src.js +39 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,21 @@
|
|
|
1
|
+
## <small>1.0.17 (2025-07-23)</small>
|
|
2
|
+
|
|
3
|
+
* feat(eslint-plugin): add no-direct-img-src rule ([4223182](https://github.com/novlan1/plugin-light/commits/4223182))
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
## <small>1.0.16 (2025-07-18)</small>
|
|
8
|
+
|
|
9
|
+
* feat(eslint): add no-todo-comment rule ([8b555b7](https://github.com/novlan1/plugin-light/commits/8b555b7))
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
## <small>1.0.15 (2025-06-16)</small>
|
|
14
|
+
|
|
15
|
+
* feat(eslint-plugin-light): add img v-lazy ([5807b47](https://github.com/novlan1/plugin-light/commits/5807b47))
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
1
19
|
## <small>1.0.14 (2025-06-11)</small>
|
|
2
20
|
|
|
3
21
|
* feat(eslint-plugin-light): 优化classname-per-line ([fa99594](https://github.com/novlan1/plugin-light/commits/fa99594))
|
package/README.md
CHANGED
|
@@ -412,6 +412,82 @@ module.exports = {
|
|
|
412
412
|
| ------ | -------------------------------- | ------ |
|
|
413
413
|
| counts | 检查阈值,小于阈值时,可以在一行 | `3` |
|
|
414
414
|
|
|
415
|
+
### 3.12. img-v-lazy
|
|
416
|
+
|
|
417
|
+
强制 `img` 标签使用 `v-lazy` 而不是 `:src`,可用于 Vue 项目中。
|
|
418
|
+
|
|
419
|
+
Usage:
|
|
420
|
+
|
|
421
|
+
```js
|
|
422
|
+
// .eslintrc.js
|
|
423
|
+
|
|
424
|
+
module.exports = {
|
|
425
|
+
plugins: [
|
|
426
|
+
'light',
|
|
427
|
+
],
|
|
428
|
+
rules: {
|
|
429
|
+
'light/img-v-lazy': 2,
|
|
430
|
+
},
|
|
431
|
+
}
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
### 3.13. no-direct-img-src
|
|
435
|
+
|
|
436
|
+
在 `jsx/tsx` 中禁止直接使用 `img src`,必须使用封装的 [`CdnImage` 组件](https://novlan1.github.io/docs/press-pix/zh-CN/cdn-image.html),可用于 React 项目中。
|
|
437
|
+
|
|
438
|
+
与 [img-v-lazy 规则](#_3-12-img-v-lazy) 类似,都是为了防止直接使用 COS 图片,而不是 CDN 图片。
|
|
439
|
+
|
|
440
|
+
Usage:
|
|
441
|
+
|
|
442
|
+
```js
|
|
443
|
+
// .eslintrc.js
|
|
444
|
+
|
|
445
|
+
module.exports = {
|
|
446
|
+
plugins: [
|
|
447
|
+
'light',
|
|
448
|
+
],
|
|
449
|
+
rules: {
|
|
450
|
+
'light/no-direct-img-src': 2,
|
|
451
|
+
|
|
452
|
+
// 传入参数
|
|
453
|
+
'light/no-direct-img-src': [2, {
|
|
454
|
+
componentName: 'MyCdnImage',
|
|
455
|
+
}],
|
|
456
|
+
},
|
|
457
|
+
}
|
|
458
|
+
```
|
|
459
|
+
|
|
460
|
+
配置选项:
|
|
461
|
+
|
|
462
|
+
| 字段 | 说明 | 默认值 |
|
|
463
|
+
| ------------- | ---------------------- | ---------- |
|
|
464
|
+
| componentName | 提示词中的自定义组件名 | `CdnImage` |
|
|
465
|
+
|
|
466
|
+
### 3.14. no-todo-comment
|
|
467
|
+
|
|
468
|
+
不允许存在 TODO。可防止调试代码被意外带到线上。
|
|
469
|
+
|
|
470
|
+
Usage:
|
|
471
|
+
|
|
472
|
+
```js
|
|
473
|
+
// .eslintrc.js
|
|
474
|
+
|
|
475
|
+
module.exports = {
|
|
476
|
+
plugins: [
|
|
477
|
+
'light',
|
|
478
|
+
],
|
|
479
|
+
rules: {
|
|
480
|
+
'light/no-todo-comment': 2,
|
|
481
|
+
},
|
|
482
|
+
}
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
配置选项:
|
|
486
|
+
|
|
487
|
+
| 字段 | 说明 | 默认值 |
|
|
488
|
+
| ------- | ---------- | ------- |
|
|
489
|
+
| keyword | 检查关键词 | `TODO:` |
|
|
490
|
+
|
|
415
491
|
## 4. 更新日志
|
|
416
492
|
|
|
417
493
|
[点此查看](./CHANGELOG.md)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
const utils = require('eslint-plugin-vue/lib/utils');
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: 'Enforce using v-lazy instead of :src on img tags',
|
|
8
|
+
category: 'Best Practices',
|
|
9
|
+
recommended: true,
|
|
10
|
+
},
|
|
11
|
+
fixable: 'code',
|
|
12
|
+
schema: [],
|
|
13
|
+
},
|
|
14
|
+
create(context) {
|
|
15
|
+
return utils.defineTemplateBodyVisitor(context, {
|
|
16
|
+
'VElement[name=\'img\']'(node) {
|
|
17
|
+
const srcBinding = node.startTag.attributes.find(attr => attr.directive
|
|
18
|
+
&& attr.key.name
|
|
19
|
+
&& attr.key.name.name === 'bind'
|
|
20
|
+
&& attr.key.argument
|
|
21
|
+
&& attr.key.argument.name === 'src');
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
const hasVLazy = node.startTag.attributes.some(attr => attr.directive
|
|
25
|
+
&& attr.key.name.name === 'lazy');
|
|
26
|
+
|
|
27
|
+
if (srcBinding && !hasVLazy) {
|
|
28
|
+
context.report({
|
|
29
|
+
node,
|
|
30
|
+
message: 'Use v-lazy instead of :src for image loading',
|
|
31
|
+
fix(fixer) {
|
|
32
|
+
// 获取 :src 绑定的完整表达式文本
|
|
33
|
+
const srcText = context.getSourceCode().getText(srcBinding.value.expression);
|
|
34
|
+
|
|
35
|
+
// 获取 :src 属性的完整文本
|
|
36
|
+
const fullSrcText = context.getSourceCode().getText(srcBinding);
|
|
37
|
+
|
|
38
|
+
// 计算替换文本
|
|
39
|
+
let replacement;
|
|
40
|
+
if (fullSrcText.startsWith(':')) {
|
|
41
|
+
replacement = `v-lazy="${srcText}"`;
|
|
42
|
+
} else if (fullSrcText.startsWith('v-bind:')) {
|
|
43
|
+
replacement = `v-lazy="${srcText}"`;
|
|
44
|
+
} else {
|
|
45
|
+
replacement = `v-lazy="${srcText}"`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 精确替换,不添加额外空格
|
|
49
|
+
return fixer.replaceText(srcBinding, replacement);
|
|
50
|
+
},
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
});
|
|
55
|
+
},
|
|
56
|
+
};
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
const DEFAULT_COMPONENT_NAME = 'CdnImage';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: '禁止直接使用 img src,必须使用封装的自定义组件',
|
|
8
|
+
category: 'Best Practices',
|
|
9
|
+
recommended: true,
|
|
10
|
+
},
|
|
11
|
+
fixable: 'code',
|
|
12
|
+
schema: [
|
|
13
|
+
{
|
|
14
|
+
type: 'object',
|
|
15
|
+
properties: {
|
|
16
|
+
componentName: {
|
|
17
|
+
type: 'string',
|
|
18
|
+
default: DEFAULT_COMPONENT_NAME,
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
additionalProperties: false,
|
|
22
|
+
},
|
|
23
|
+
],
|
|
24
|
+
},
|
|
25
|
+
create(context) {
|
|
26
|
+
return {
|
|
27
|
+
JSXOpeningElement(node) {
|
|
28
|
+
if (node.name.name === 'img') {
|
|
29
|
+
const srcAttribute = node.attributes.find(attr => attr.type === 'JSXAttribute' && attr.name.name === 'src');
|
|
30
|
+
const options = context.options[0] || {};
|
|
31
|
+
const componentName = options.componentName ?? DEFAULT_COMPONENT_NAME;
|
|
32
|
+
|
|
33
|
+
if (srcAttribute) {
|
|
34
|
+
context.report({
|
|
35
|
+
node,
|
|
36
|
+
message: `禁止直接使用 img src,必须使用封装的 ${componentName} 组件`,
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
};
|
|
42
|
+
},
|
|
43
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
const DEFAULT_KEYWORD = 'TODO:';
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
meta: {
|
|
5
|
+
type: 'problem',
|
|
6
|
+
docs: {
|
|
7
|
+
description: '禁止使用未规范的TODO注释',
|
|
8
|
+
category: 'Best Practices',
|
|
9
|
+
recommended: true,
|
|
10
|
+
},
|
|
11
|
+
schema: [
|
|
12
|
+
{
|
|
13
|
+
type: 'object',
|
|
14
|
+
properties: {
|
|
15
|
+
keyword: {
|
|
16
|
+
type: 'string',
|
|
17
|
+
default: DEFAULT_KEYWORD,
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
additionalProperties: false,
|
|
21
|
+
},
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
create(context) {
|
|
25
|
+
const sourceCode = context.getSourceCode();
|
|
26
|
+
const comments = sourceCode.getAllComments();
|
|
27
|
+
|
|
28
|
+
comments.forEach((comment) => {
|
|
29
|
+
const options = context.options[0] || {};
|
|
30
|
+
const keyword = options.keyword ?? DEFAULT_KEYWORD;
|
|
31
|
+
|
|
32
|
+
if (comment.value.includes(keyword)) {
|
|
33
|
+
context.report({
|
|
34
|
+
node: comment,
|
|
35
|
+
message: '不允许 TODO 存在',
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
return {};
|
|
41
|
+
},
|
|
42
|
+
};
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
const { RuleTester } = require('eslint');
|
|
2
|
+
|
|
3
|
+
const rule = require('../rules/no-direct-img-src');
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
const ruleTester = new RuleTester({
|
|
7
|
+
parserOptions: {
|
|
8
|
+
ecmaVersion: 'latest',
|
|
9
|
+
sourceType: 'module',
|
|
10
|
+
ecmaFeatures: {
|
|
11
|
+
jsx: true,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
const ERROR_MESSAGE = '禁止直接使用 img src,必须使用封装的 CdnImage 组件';
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
ruleTester.run('no-direct-img-src', rule, {
|
|
20
|
+
valid: [
|
|
21
|
+
{
|
|
22
|
+
code: '<CdnImage src="image.png" alt="example" />',
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
code: '<div><CdnImage src="image.jpg" /></div>',
|
|
26
|
+
},
|
|
27
|
+
],
|
|
28
|
+
invalid: [
|
|
29
|
+
{
|
|
30
|
+
code: '<img src="image.png" alt="example" />',
|
|
31
|
+
errors: [{ message: ERROR_MESSAGE }],
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
code: '<img src={require("./image.jpg")} className="img" />',
|
|
35
|
+
errors: [{ message: ERROR_MESSAGE }],
|
|
36
|
+
},
|
|
37
|
+
],
|
|
38
|
+
});
|
|
39
|
+
|