Skip to content

Commit 325a777

Browse files
committed
Fix vuejs#1373: Add rule no-invalid-attribute-name
1 parent a473a0d commit 325a777

File tree

5 files changed

+258
-0
lines changed

5 files changed

+258
-0
lines changed

docs/rules/README.md

+2
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ sidebarDepth: 0
1212
:bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions).
1313
:::
1414

15+
1516
## Base Rules (Enabling Correct ESLint Parsing)
1617

1718
Enforce all the rules in this category, as well as all higher priority rules, with:
@@ -326,6 +327,7 @@ For example:
326327
| [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | |
327328
| [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty | |
328329
| [vue/no-expose-after-await](./no-expose-after-await.md) | disallow asynchronously registered `expose` | |
330+
| [vue/no-invalid-attribute-name](./no-invalid-attribute-name.md) | require the attributes to match the imported component name | |
329331
| [vue/no-invalid-model-keys](./no-invalid-model-keys.md) | require valid keys in model option | |
330332
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | |
331333
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | :bulb: |
+43
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
---
2+
pageClass: rule-details
3+
sidebarDepth: 0
4+
title: vue/no-invalid-attribute-name
5+
description: require the attributes to match the imported component name
6+
---
7+
# vue/no-invalid-attribute-name
8+
9+
> require the attributes to match the imported component name
10+
11+
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>
12+
13+
## :book: Rule Details
14+
15+
This rule detects invalid html attributes.
16+
17+
<eslint-code-block :rules="{'vue/no-invalid-attribute-name': ['error']}">
18+
19+
```vue
20+
<template>
21+
<!-- ✓ GOOD -->
22+
<p foo.bar></p>
23+
<p foo-bar></p>
24+
<p _foo.bar></p>
25+
<p :foo-bar></p>
26+
27+
<!-- ✗ BAD -->
28+
<p 0abc></p>
29+
<p -def></p>
30+
<p !ghi></p>
31+
</template>
32+
```
33+
34+
</eslint-code-block>
35+
36+
## :wrench: Options
37+
38+
Nothing.
39+
40+
## :mag: Implementation
41+
42+
- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-invalid-attribute-name.js)
43+
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-invalid-attribute-name.js)

lib/index.js

+1
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,7 @@ module.exports = {
9393
'no-export-in-script-setup': require('./rules/no-export-in-script-setup'),
9494
'no-expose-after-await': require('./rules/no-expose-after-await'),
9595
'no-extra-parens': require('./rules/no-extra-parens'),
96+
'no-invalid-attribute-name': require('./rules/no-invalid-attribute-name'),
9697
'no-invalid-model-keys': require('./rules/no-invalid-model-keys'),
9798
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
9899
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/**
2+
* @author Doug Wade <[email protected]>
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
module.exports = {
10+
meta: {
11+
type: 'problem',
12+
docs: {
13+
description:
14+
'require the attributes to match the imported component name',
15+
categories: undefined,
16+
url: 'https://eslint.vuejs.org/rules/no-invalid-attribute-name.html'
17+
},
18+
fixable: null,
19+
schema: [],
20+
messages: {
21+
unexpected: '{{name}} is not a valid attribute name'
22+
}
23+
},
24+
/** @param {RuleContext} context */
25+
create(context) {
26+
/**
27+
* @param {string | VIdentifier} key
28+
* @return {string}
29+
*/
30+
const getName = (key) => {
31+
if (typeof key === 'string') {
32+
return key
33+
}
34+
35+
return key.name
36+
}
37+
38+
return utils.defineTemplateBodyVisitor(context, {
39+
VAttribute(node) {
40+
const name = getName(node.key.name)
41+
42+
if (!/^[_:a-zA-Z][_:.\-a-zA-Z0-9]+/.test(name)) {
43+
context.report({
44+
node,
45+
messageId: 'unexpected',
46+
data: {
47+
name
48+
}
49+
})
50+
}
51+
}
52+
})
53+
}
54+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,158 @@
1+
/**
2+
* @author *****your name*****
3+
* See LICENSE file in root directory for full license.
4+
*/
5+
'use strict'
6+
7+
const RuleTester = require('eslint').RuleTester
8+
const rule = require('../../../lib/rules/no-invalid-attribute-name')
9+
10+
const tester = new RuleTester({
11+
parser: require.resolve('vue-eslint-parser'),
12+
parserOptions: {
13+
ecmaVersion: 2020,
14+
sourceType: 'module'
15+
}
16+
})
17+
18+
tester.run('no-invalid-attribute-name', rule, {
19+
valid: [
20+
{
21+
filename: 'test.vue',
22+
code: `
23+
<template>
24+
<div>
25+
<p foo>
26+
{{ content }}
27+
</p>
28+
</div>
29+
</template>
30+
`
31+
},
32+
{
33+
filename: 'test.vue',
34+
code: `
35+
<template>
36+
<div>
37+
<p foo="bar">
38+
{{ content }}
39+
</p>
40+
</div>
41+
</template>
42+
`
43+
},
44+
{
45+
filename: 'test.vue',
46+
code: `
47+
<template>
48+
<div>
49+
<p foo-bar>
50+
{{ content }}
51+
</p>
52+
</div>
53+
</template>
54+
`
55+
},
56+
{
57+
filename: 'test.vue',
58+
code: `
59+
<template>
60+
<div>
61+
<p _foo-bar>
62+
{{ content }}
63+
</p>
64+
</div>
65+
</template>
66+
`
67+
},
68+
{
69+
filename: 'test.vue',
70+
code: `
71+
<template>
72+
<div>
73+
<p :foo-bar>
74+
{{ content }}
75+
</p>
76+
</div>
77+
</template>
78+
`
79+
},
80+
{
81+
filename: 'test.vue',
82+
code: `
83+
<template>
84+
<div>
85+
<p foo.bar>
86+
{{ content }}
87+
</p>
88+
</div>
89+
</template>
90+
`
91+
},
92+
{
93+
filename: 'test.vue',
94+
code: `
95+
<template>
96+
<div>
97+
<p quux-.9>
98+
{{ content }}
99+
</p>
100+
</div>
101+
</template>
102+
`
103+
}
104+
],
105+
invalid: [
106+
{
107+
filename: 'test.vue',
108+
code: `
109+
<template>
110+
<div>
111+
<p 0abc>
112+
{{ content }}
113+
</p>
114+
</div>
115+
</template>
116+
`,
117+
errors: [
118+
{
119+
message: '0abc is not a valid attribute name'
120+
}
121+
]
122+
},
123+
{
124+
filename: 'test.vue',
125+
code: `
126+
<template>
127+
<div>
128+
<p -def>
129+
{{ content }}
130+
</p>
131+
</div>
132+
</template>
133+
`,
134+
errors: [
135+
{
136+
message: '-def is not a valid attribute name'
137+
}
138+
]
139+
},
140+
{
141+
filename: 'test.vue',
142+
code: `
143+
<template>
144+
<div>
145+
<p !ghi>
146+
{{ content }}
147+
</p>
148+
</div>
149+
</template>
150+
`,
151+
errors: [
152+
{
153+
message: '!ghi is not a valid attribute name'
154+
}
155+
]
156+
}
157+
]
158+
})

0 commit comments

Comments
 (0)