@foundbyte/eslint-plugin 1.0.5 → 1.0.7
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/README.md +232 -0
- package/index.js +2 -0
- package/lib/rules/index.d.ts +6 -0
- package/lib/rules/tag-attribute-restriction.d.ts +9 -0
- package/lib/rules/tag-attribute-restriction.js +304 -0
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
# @foundbyte/eslint-plugin
|
|
2
|
+
|
|
3
|
+
一个为 FoundByte 项目定制的 ESLint 插件,提供了一系列代码质量检查和最佳实践规则。
|
|
4
|
+
|
|
5
|
+
## 安装
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @foundbyte/eslint-plugin --save-dev
|
|
9
|
+
# 或
|
|
10
|
+
yarn add @foundbyte/eslint-plugin --dev
|
|
11
|
+
# 或
|
|
12
|
+
pnpm add @foundbyte/eslint-plugin --dev
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 使用
|
|
16
|
+
|
|
17
|
+
在 ESLint 配置文件中添加插件:
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
// .eslintrc.js
|
|
21
|
+
module.exports = {
|
|
22
|
+
plugins: ['@foundbyte'],
|
|
23
|
+
rules: {
|
|
24
|
+
// 启用所有推荐的规则
|
|
25
|
+
'@foundbyte/arrow-function-padding': 'error',
|
|
26
|
+
'@foundbyte/hooks-arrow-function-only': 'error',
|
|
27
|
+
'@foundbyte/type-comment-requirement': ['error', {
|
|
28
|
+
minCommentsPerGroup: 1,
|
|
29
|
+
groupSize: 8,
|
|
30
|
+
includeTypes: true,
|
|
31
|
+
includeInterfaces: true,
|
|
32
|
+
}],
|
|
33
|
+
'@foundbyte/hooks-export-limit': ['error', { maxExports: 20 }],
|
|
34
|
+
'@foundbyte/vue-script-lines-limit': ['error', { maxLines: 300 }],
|
|
35
|
+
'@foundbyte/tag-attribute-restriction': ['error', {
|
|
36
|
+
restrictions: [
|
|
37
|
+
{
|
|
38
|
+
tag: 'image',
|
|
39
|
+
attributes: [
|
|
40
|
+
{ name: 'mode', disallowedValues: [''] },
|
|
41
|
+
]
|
|
42
|
+
}
|
|
43
|
+
]
|
|
44
|
+
}],
|
|
45
|
+
},
|
|
46
|
+
}
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## 规则列表与配置示例
|
|
50
|
+
|
|
51
|
+
### arrow-function-padding
|
|
52
|
+
|
|
53
|
+
要求函数体之间必须空行。
|
|
54
|
+
|
|
55
|
+
**配置示例:**
|
|
56
|
+
```javascript
|
|
57
|
+
'@foundbyte/arrow-function-padding': 'error' // 总是要求空行
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**示例:**
|
|
61
|
+
```javascript
|
|
62
|
+
// ✅ 正确
|
|
63
|
+
const one = () => {}
|
|
64
|
+
|
|
65
|
+
const two = () => {}
|
|
66
|
+
|
|
67
|
+
// ❌ 错误
|
|
68
|
+
const one = () => {}
|
|
69
|
+
const two = () => {}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### hooks-arrow-function-only
|
|
73
|
+
|
|
74
|
+
要求 React/Vue Hooks 必须使用箭头函数。
|
|
75
|
+
|
|
76
|
+
**示例:**
|
|
77
|
+
```javascript
|
|
78
|
+
// ✅ 正确
|
|
79
|
+
const useMyHook = () => {
|
|
80
|
+
const [state, setState] = useState()
|
|
81
|
+
return state
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
// ❌ 错误
|
|
85
|
+
function useMyHook() {
|
|
86
|
+
const [state, setState] = useState()
|
|
87
|
+
return state
|
|
88
|
+
}
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
### hooks-export-limit
|
|
92
|
+
|
|
93
|
+
限制 Hooks 文件内部成员的导出数量。
|
|
94
|
+
|
|
95
|
+
**配置示例:**
|
|
96
|
+
```javascript
|
|
97
|
+
'@foundbyte/hooks-export-limit': ['error', { maxExports: 2 }] // 最大允许的导出数量
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
**示例:**
|
|
101
|
+
```javascript
|
|
102
|
+
// ✅ 正确 - 只有一个导出
|
|
103
|
+
export const useMyHook = () => {
|
|
104
|
+
const a = ''
|
|
105
|
+
return {
|
|
106
|
+
a
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
// ❌ 错误 - 多个导出
|
|
111
|
+
export const useMyHook = () => {
|
|
112
|
+
const a = ''
|
|
113
|
+
const b = ''
|
|
114
|
+
return {
|
|
115
|
+
a,
|
|
116
|
+
b
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### type-comment-requirement
|
|
122
|
+
|
|
123
|
+
要求复杂类型定义必须有类型注释。
|
|
124
|
+
|
|
125
|
+
**配置示例:**
|
|
126
|
+
```javascript
|
|
127
|
+
'@foundbyte/type-comment-requirement': ['error', {
|
|
128
|
+
/** 至少1个注释 */
|
|
129
|
+
minCommentsPerGroup: 1,
|
|
130
|
+
/** 每3个类型 */
|
|
131
|
+
groupSize: 3,
|
|
132
|
+
}]
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
**示例:**
|
|
136
|
+
```ts
|
|
137
|
+
// ✅ 正确 - 有类型注释
|
|
138
|
+
interface user {
|
|
139
|
+
/** 姓名 */
|
|
140
|
+
name: 'John'
|
|
141
|
+
/** 年龄 */
|
|
142
|
+
age: 30
|
|
143
|
+
ddress: { city: 'NY', zip: '10001' }
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// ❌ 错误 - 复杂对象没有类型注释
|
|
147
|
+
interface user { name: 'John', age: 30, address: { city: 'NY', zip: '10001' } }
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### vue-script-lines-limit
|
|
151
|
+
|
|
152
|
+
限制 Vue 单文件组件中 `<script>` 标签的行数。
|
|
153
|
+
|
|
154
|
+
**配置示例:**
|
|
155
|
+
```javascript
|
|
156
|
+
'@foundbyte/vue-script-lines-limit': ['error', { maxLines: 200 }] // 最大允许的行数
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
**示例:**
|
|
160
|
+
```vue
|
|
161
|
+
<!-- ✅ 正确 - script 标签行数在限制内 -->
|
|
162
|
+
<script>
|
|
163
|
+
export default {
|
|
164
|
+
data() {
|
|
165
|
+
return { count: 0 }
|
|
166
|
+
},
|
|
167
|
+
methods: {
|
|
168
|
+
increment() {
|
|
169
|
+
this.count++
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
</script>
|
|
174
|
+
|
|
175
|
+
<!-- ❌ 错误 - script 标签行数超过限制 -->
|
|
176
|
+
<script>
|
|
177
|
+
// ... 超过 200 行代码 ...
|
|
178
|
+
</script>
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
### tag-attribute-restriction
|
|
182
|
+
|
|
183
|
+
限制特定标签使用某些属性或属性值。
|
|
184
|
+
|
|
185
|
+
**配置示例:**
|
|
186
|
+
```javascript
|
|
187
|
+
'@foundbyte/tag-attribute-restriction': ['error', {
|
|
188
|
+
restrictions: [
|
|
189
|
+
{
|
|
190
|
+
tag: 'image', // 标签名称
|
|
191
|
+
attributes: [ // 属性限制列表
|
|
192
|
+
{
|
|
193
|
+
name: 'mode', // 属性名称
|
|
194
|
+
disallowedValues: [ // 禁止的值列表(可选)
|
|
195
|
+
'',
|
|
196
|
+
'scaleToFill'
|
|
197
|
+
]
|
|
198
|
+
},
|
|
199
|
+
{
|
|
200
|
+
name: 'lazy-load' // 完全禁止该属性
|
|
201
|
+
}
|
|
202
|
+
]
|
|
203
|
+
}
|
|
204
|
+
]
|
|
205
|
+
}]
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
**支持的语法:**
|
|
209
|
+
- 静态属性: `<image mode="" />`
|
|
210
|
+
- 动态属性: `<image :mode="'scaleToFill'" />`
|
|
211
|
+
- 无值属性: `<image lazy-load />`
|
|
212
|
+
- 连字符属性名: `lazy-load`, `data-*` 等
|
|
213
|
+
- JSX 属性
|
|
214
|
+
|
|
215
|
+
**示例:**
|
|
216
|
+
```vue
|
|
217
|
+
<!-- ✅ 正确 -->
|
|
218
|
+
<image src="test.jpg" mode="aspectFit" />
|
|
219
|
+
|
|
220
|
+
<!-- ❌ 错误 - 禁止使用空字符串的 mode 属性 -->
|
|
221
|
+
<image mode="" />
|
|
222
|
+
|
|
223
|
+
<!-- ❌ 错误 - 禁止使用 scaleToFill 值 -->
|
|
224
|
+
<image :mode="'scaleToFill'" />
|
|
225
|
+
|
|
226
|
+
<!-- ❌ 错误 - 完全禁止 lazy-load 属性 -->
|
|
227
|
+
<image lazy-load />
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
**错误消息:**
|
|
231
|
+
- `标签 <image> 不允许使用属性 mode` - 当属性被完全禁止时
|
|
232
|
+
- `标签 <image> 的属性 mode 不允许使用值 ""` - 当特定属性值被禁止时
|
package/index.js
CHANGED
|
@@ -3,6 +3,7 @@ const vueScriptLinesLimitRule = require('./lib/rules/vue-script-lines-limit')
|
|
|
3
3
|
const typeCommentRequirementRule = require('./lib/rules/type-comment-requirement')
|
|
4
4
|
const hooksExportLimitRule = require('./lib/rules/hooks-export-limit')
|
|
5
5
|
const hooksArrowFunctionOnlyRule = require('./lib/rules/hooks-arrow-function-only')
|
|
6
|
+
const tagAttributeRestrictionRule = require('./lib/rules/tag-attribute-restriction')
|
|
6
7
|
|
|
7
8
|
module.exports = {
|
|
8
9
|
rules: {
|
|
@@ -11,5 +12,6 @@ module.exports = {
|
|
|
11
12
|
'type-comment-requirement': typeCommentRequirementRule,
|
|
12
13
|
'hooks-export-limit': hooksExportLimitRule,
|
|
13
14
|
'hooks-arrow-function-only': hooksArrowFunctionOnlyRule,
|
|
15
|
+
'tag-attribute-restriction': tagAttributeRestrictionRule,
|
|
14
16
|
},
|
|
15
17
|
}
|
package/lib/rules/index.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export { HooksExportLimitOptions } from './hooks-export-limit'
|
|
|
3
3
|
export { TypeCommentRequirementOptions } from './type-comment-requirement'
|
|
4
4
|
export { VueScriptLinesLimitOptions } from './vue-script-lines-limit'
|
|
5
5
|
export { HooksArrowFunctionOnlyOptions } from './hooks-arrow-function-only'
|
|
6
|
+
export { TagAttributeRestrictionOptions } from './tag-attribute-restriction'
|
|
6
7
|
|
|
7
8
|
import { Rule } from 'eslint'
|
|
8
9
|
|
|
@@ -35,6 +36,11 @@ export interface Rules {
|
|
|
35
36
|
*/
|
|
36
37
|
'hooks-arrow-function-only': Rule.RuleModule
|
|
37
38
|
|
|
39
|
+
/**
|
|
40
|
+
* 限制特定标签使用某些属性或属性值
|
|
41
|
+
*/
|
|
42
|
+
'tag-attribute-restriction': Rule.RuleModule
|
|
43
|
+
|
|
38
44
|
/**
|
|
39
45
|
* 索引签名,用于兼容 ESLint 插件类型
|
|
40
46
|
*/
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
meta: {
|
|
3
|
+
type: 'problem',
|
|
4
|
+
docs: {
|
|
5
|
+
description: '限制特定标签使用某些属性或属性值',
|
|
6
|
+
category: 'Best Practices',
|
|
7
|
+
recommended: false,
|
|
8
|
+
},
|
|
9
|
+
fixable: null,
|
|
10
|
+
schema: [
|
|
11
|
+
{
|
|
12
|
+
type: 'object',
|
|
13
|
+
properties: {
|
|
14
|
+
restrictions: {
|
|
15
|
+
type: 'array',
|
|
16
|
+
items: {
|
|
17
|
+
type: 'object',
|
|
18
|
+
properties: {
|
|
19
|
+
tag: {
|
|
20
|
+
type: 'string',
|
|
21
|
+
},
|
|
22
|
+
attributes: {
|
|
23
|
+
type: 'array',
|
|
24
|
+
items: {
|
|
25
|
+
type: 'object',
|
|
26
|
+
properties: {
|
|
27
|
+
name: {
|
|
28
|
+
type: 'string',
|
|
29
|
+
},
|
|
30
|
+
disallowedValues: {
|
|
31
|
+
type: 'array',
|
|
32
|
+
items: {
|
|
33
|
+
type: 'string',
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
required: ['name'],
|
|
38
|
+
additionalProperties: false,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
required: ['tag', 'attributes'],
|
|
43
|
+
additionalProperties: false,
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
additionalProperties: false,
|
|
48
|
+
},
|
|
49
|
+
],
|
|
50
|
+
messages: {
|
|
51
|
+
disallowedAttribute: '标签 <{{tag}}> 不允许使用属性 {{attribute}}',
|
|
52
|
+
disallowedAttributeValue: '标签 <{{tag}}> 的属性 {{attribute}} 不允许使用值 "{{value}}"',
|
|
53
|
+
},
|
|
54
|
+
},
|
|
55
|
+
|
|
56
|
+
create(context) {
|
|
57
|
+
const options = context.options[0] || {};
|
|
58
|
+
const restrictions = options.restrictions || [];
|
|
59
|
+
|
|
60
|
+
// 如果没有配置限制,则不进行检查
|
|
61
|
+
if (restrictions.length === 0) {
|
|
62
|
+
return {};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
// 获取文件名,只对 .vue 文件生效
|
|
66
|
+
const filename = context.getFilename();
|
|
67
|
+
if (!filename.endsWith('.vue')) {
|
|
68
|
+
return {};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// 创建标签到限制规则的映射
|
|
72
|
+
const tagRestrictions = new Map();
|
|
73
|
+
restrictions.forEach(restriction => {
|
|
74
|
+
tagRestrictions.set(restriction.tag.toLowerCase(), restriction);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
Program(node) {
|
|
79
|
+
// 获取整个文件的源代码
|
|
80
|
+
const sourceCode = context.getSourceCode();
|
|
81
|
+
const text = sourceCode.getText();
|
|
82
|
+
|
|
83
|
+
// 使用正则表达式匹配模板中的标签和属性
|
|
84
|
+
const tagRegex = /<(\w+)([^>]*)>/g;
|
|
85
|
+
let match;
|
|
86
|
+
|
|
87
|
+
while ((match = tagRegex.exec(text)) !== null) {
|
|
88
|
+
const tagName = match[1].toLowerCase();
|
|
89
|
+
const attributesStr = match[2];
|
|
90
|
+
|
|
91
|
+
const restriction = tagRestrictions.get(tagName);
|
|
92
|
+
if (restriction) {
|
|
93
|
+
checkTemplateAttributes(tagName, attributesStr, restriction.attributes, match.index, sourceCode);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
JSXOpeningElement(node) {
|
|
99
|
+
// 处理 JSX 元素
|
|
100
|
+
if (node.name.type === 'JSXIdentifier') {
|
|
101
|
+
const tagName = node.name.name.toLowerCase();
|
|
102
|
+
const restriction = tagRestrictions.get(tagName);
|
|
103
|
+
|
|
104
|
+
if (restriction) {
|
|
105
|
+
checkAttributes(node, tagName, restriction.attributes);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
|
|
111
|
+
function checkTemplateAttributes(tagName, attributesStr, attributeRestrictions, tagStartIndex, sourceCode) {
|
|
112
|
+
// 使用正则表达式匹配属性(支持静态和动态属性,支持连字符属性名)
|
|
113
|
+
const attrRegex = /(:)?([\w-]+)(?:\s*=\s*"([^"]*)")?/g;
|
|
114
|
+
let attrMatch;
|
|
115
|
+
|
|
116
|
+
while ((attrMatch = attrRegex.exec(attributesStr)) !== null) {
|
|
117
|
+
const isDynamic = attrMatch[1] === ':'; // 是否是动态属性
|
|
118
|
+
const attributeName = attrMatch[2].toLowerCase();
|
|
119
|
+
let attributeValue = attrMatch[3] || '';
|
|
120
|
+
|
|
121
|
+
// 处理动态属性中的字符串值
|
|
122
|
+
if (isDynamic) {
|
|
123
|
+
// 处理空字符串值
|
|
124
|
+
if (attributeValue === "''") {
|
|
125
|
+
attributeValue = '';
|
|
126
|
+
}
|
|
127
|
+
// 处理带引号的字符串值,如 'scaleToFill'
|
|
128
|
+
else if (attributeValue.startsWith("'") && attributeValue.endsWith("'")) {
|
|
129
|
+
attributeValue = attributeValue.slice(1, -1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
attributeRestrictions.forEach(attrRestriction => {
|
|
134
|
+
if (attributeName === attrRestriction.name.toLowerCase()) {
|
|
135
|
+
const disallowedValues = attrRestriction.disallowedValues || [];
|
|
136
|
+
|
|
137
|
+
// 如果属性存在但没有指定禁止的值,则报告错误
|
|
138
|
+
if (disallowedValues.length === 0) {
|
|
139
|
+
context.report({
|
|
140
|
+
loc: {
|
|
141
|
+
start: sourceCode.getLocFromIndex(tagStartIndex + attrMatch.index),
|
|
142
|
+
end: sourceCode.getLocFromIndex(tagStartIndex + attrMatch.index + attrMatch[0].length),
|
|
143
|
+
},
|
|
144
|
+
messageId: 'disallowedAttribute',
|
|
145
|
+
data: {
|
|
146
|
+
tag: tagName,
|
|
147
|
+
attribute: attrRestriction.name,
|
|
148
|
+
},
|
|
149
|
+
});
|
|
150
|
+
} else if (disallowedValues.includes(attributeValue)) {
|
|
151
|
+
context.report({
|
|
152
|
+
loc: {
|
|
153
|
+
start: sourceCode.getLocFromIndex(tagStartIndex + attrMatch.index),
|
|
154
|
+
end: sourceCode.getLocFromIndex(tagStartIndex + attrMatch.index + attrMatch[0].length),
|
|
155
|
+
},
|
|
156
|
+
messageId: 'disallowedAttributeValue',
|
|
157
|
+
data: {
|
|
158
|
+
tag: tagName,
|
|
159
|
+
attribute: attrRestriction.name,
|
|
160
|
+
value: attributeValue,
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function checkAttributes(node, tagName, attributeRestrictions) {
|
|
170
|
+
const attributes = getAttributes(node);
|
|
171
|
+
|
|
172
|
+
attributeRestrictions.forEach(attrRestriction => {
|
|
173
|
+
const attributeName = attrRestriction.name;
|
|
174
|
+
const disallowedValues = attrRestriction.disallowedValues || [];
|
|
175
|
+
|
|
176
|
+
// 检查属性是否存在
|
|
177
|
+
const attribute = attributes.find(attr =>
|
|
178
|
+
attr.name.toLowerCase() === attributeName.toLowerCase()
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
if (attribute) {
|
|
182
|
+
// 如果属性存在但没有指定禁止的值,则报告错误
|
|
183
|
+
if (disallowedValues.length === 0) {
|
|
184
|
+
context.report({
|
|
185
|
+
node: attribute.node,
|
|
186
|
+
messageId: 'disallowedAttribute',
|
|
187
|
+
data: {
|
|
188
|
+
tag: tagName,
|
|
189
|
+
attribute: attributeName,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
} else if (attribute.value) {
|
|
193
|
+
// 检查属性值是否在禁止列表中
|
|
194
|
+
const attributeValue = getAttributeValue(attribute.value);
|
|
195
|
+
if (disallowedValues.includes(attributeValue)) {
|
|
196
|
+
context.report({
|
|
197
|
+
node: attribute.node,
|
|
198
|
+
messageId: 'disallowedAttributeValue',
|
|
199
|
+
data: {
|
|
200
|
+
tag: tagName,
|
|
201
|
+
attribute: attributeName,
|
|
202
|
+
value: attributeValue,
|
|
203
|
+
},
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function getAttributes(node) {
|
|
212
|
+
const attributes = [];
|
|
213
|
+
|
|
214
|
+
// 处理 JSX 属性
|
|
215
|
+
if (node.type === 'JSXOpeningElement') {
|
|
216
|
+
node.attributes.forEach(attr => {
|
|
217
|
+
if (attr.type === 'JSXAttribute') {
|
|
218
|
+
attributes.push({
|
|
219
|
+
name: attr.name.name,
|
|
220
|
+
value: attr.value,
|
|
221
|
+
node: attr,
|
|
222
|
+
});
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
// 处理 Vue 模板属性
|
|
227
|
+
else if (node.type === 'VElement') {
|
|
228
|
+
// 处理 Vue 模板的属性
|
|
229
|
+
if (node.startTag && node.startTag.attributes) {
|
|
230
|
+
node.startTag.attributes.forEach(attr => {
|
|
231
|
+
let attributeName = '';
|
|
232
|
+
let valueNode = null;
|
|
233
|
+
|
|
234
|
+
// 处理不同类型的 Vue 属性
|
|
235
|
+
if (attr.key) {
|
|
236
|
+
// 处理静态属性
|
|
237
|
+
if (attr.key.type === 'VDirectiveKey') {
|
|
238
|
+
// 处理动态属性 (:attribute)
|
|
239
|
+
if (attr.key.name && attr.key.name.name === 'bind') {
|
|
240
|
+
if (attr.key.argument && attr.key.argument.type === 'VIdentifier') {
|
|
241
|
+
attributeName = attr.key.argument.name;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} else if (attr.key.type === 'VIdentifier') {
|
|
245
|
+
// 处理静态属性
|
|
246
|
+
attributeName = attr.key.name;
|
|
247
|
+
} else if (attr.key.name) {
|
|
248
|
+
// 处理其他类型的属性
|
|
249
|
+
attributeName = attr.key.name;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// 处理属性值
|
|
254
|
+
if (attr.value) {
|
|
255
|
+
valueNode = attr.value;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
if (attributeName) {
|
|
259
|
+
attributes.push({
|
|
260
|
+
name: attributeName,
|
|
261
|
+
value: valueNode,
|
|
262
|
+
node: attr,
|
|
263
|
+
});
|
|
264
|
+
}
|
|
265
|
+
});
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return attributes;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function getAttributeValue(valueNode) {
|
|
273
|
+
if (!valueNode) {
|
|
274
|
+
return '';
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// 处理字符串字面量
|
|
278
|
+
if (valueNode.type === 'Literal') {
|
|
279
|
+
return valueNode.value;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
// 处理 JSX 表达式容器中的字符串
|
|
283
|
+
if (valueNode.type === 'JSXExpressionContainer') {
|
|
284
|
+
if (valueNode.expression.type === 'Literal') {
|
|
285
|
+
return valueNode.expression.value;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// 处理 Vue 模板中的属性值
|
|
290
|
+
if (valueNode.type === 'VLiteral') {
|
|
291
|
+
return valueNode.value;
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// 处理 Vue 模板中的表达式
|
|
295
|
+
if (valueNode.type === 'VExpressionContainer') {
|
|
296
|
+
// 对于表达式容器,我们无法确定具体的值,返回空字符串
|
|
297
|
+
return '';
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// 对于其他类型的值,返回空字符串
|
|
301
|
+
return '';
|
|
302
|
+
}
|
|
303
|
+
},
|
|
304
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@foundbyte/eslint-plugin",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "ESLint plugin for enforcing code style rules including arrow function padding, Vue script lines limit, and TypeScript type comment requirements",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|