Skip to content

Commit

Permalink
Add vue/no-invalid-attribute-name rule (#1851)
Browse files Browse the repository at this point in the history
* Fix #1373: Add rule no-invalid-attribute-name

* Remove stray newline

* Apply suggestions from code review

Co-authored-by: Flo Edelmann <florian-edelmann@online.de>

* #1373 Use xml-name-validator

* Fix linting error

* remove stray newline

* refactor test code

* Update lib/rules/no-invalid-attribute-name.js

Co-authored-by: Flo Edelmann <florian-edelmann@online.de>

* fix bad commit from github ui

* fix typechecking error

* Respond to PR feedback

* Include the added types in package.json

* check v-bind directives

* Update tests/lib/rules/no-invalid-attribute-name.js

Co-authored-by: Flo Edelmann <florian-edelmann@online.de>

* Fix failing unit test

* Update lib/rules/no-invalid-attribute-name.js

* Update lib/rules/no-invalid-attribute-name.js

* Update tests/lib/rules/no-invalid-attribute-name.js

* Update tests/lib/rules/no-invalid-attribute-name.js

Co-authored-by: Flo Edelmann <florian-edelmann@online.de>
Co-authored-by: Yosuke Ota <otameshiyo23@gmail.com>
  • Loading branch information
3 people committed May 17, 2022
1 parent b0639d7 commit ab85fd6
Show file tree
Hide file tree
Showing 6 changed files with 263 additions and 1 deletion.
1 change: 1 addition & 0 deletions docs/rules/README.md
Expand Up @@ -224,6 +224,7 @@ For example:
| [vue/no-boolean-default](./no-boolean-default.md) | disallow boolean defaults | :wrench: | :hammer: |
| [vue/no-duplicate-attr-inheritance](./no-duplicate-attr-inheritance.md) | enforce `inheritAttrs` to be set to `false` when using `v-bind="$attrs"` | | :hammer: |
| [vue/no-empty-component-block](./no-empty-component-block.md) | disallow the `<template>` `<script>` `<style>` block to be empty | | :hammer: |
| [vue/no-invalid-attribute-name](./no-invalid-attribute-name.md) | require valid attribute names | | :warning: |
| [vue/no-multiple-objects-in-class](./no-multiple-objects-in-class.md) | disallow to pass multiple objects into array to class | | :hammer: |
| [vue/no-potential-component-option-typo](./no-potential-component-option-typo.md) | disallow a potential typo in your component property | :bulb: | :hammer: |
| [vue/no-restricted-block](./no-restricted-block.md) | disallow specific block | | :hammer: |
Expand Down
43 changes: 43 additions & 0 deletions docs/rules/no-invalid-attribute-name.md
@@ -0,0 +1,43 @@
---
pageClass: rule-details
sidebarDepth: 0
title: vue/no-invalid-attribute-name
description: require valid attribute names
---
# vue/no-invalid-attribute-name

> require valid attribute names
- :exclamation: <badge text="This rule has not been released yet." vertical="middle" type="error"> ***This rule has not been released yet.*** </badge>

## :book: Rule Details

This rule detects invalid HTML attributes.

<eslint-code-block :rules="{'vue/no-invalid-attribute-name': ['error']}">

```vue
<template>
<!-- ✓ GOOD -->
<p foo.bar></p>
<p foo-bar></p>
<p _foo.bar></p>
<p :foo-bar></p>
<!-- ✗ BAD -->
<p 0abc></p>
<p -def></p>
<p !ghi></p>
</template>
```

</eslint-code-block>

## :wrench: Options

Nothing.

## :mag: Implementation

- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/no-invalid-attribute-name.js)
- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/no-invalid-attribute-name.js)
1 change: 1 addition & 0 deletions lib/index.js
Expand Up @@ -91,6 +91,7 @@ module.exports = {
'no-export-in-script-setup': require('./rules/no-export-in-script-setup'),
'no-expose-after-await': require('./rules/no-expose-after-await'),
'no-extra-parens': require('./rules/no-extra-parens'),
'no-invalid-attribute-name': require('./rules/no-invalid-attribute-name'),
'no-invalid-model-keys': require('./rules/no-invalid-model-keys'),
'no-irregular-whitespace': require('./rules/no-irregular-whitespace'),
'no-lifecycle-after-await': require('./rules/no-lifecycle-after-await'),
Expand Down
69 changes: 69 additions & 0 deletions lib/rules/no-invalid-attribute-name.js
@@ -0,0 +1,69 @@
/**
* @author Doug Wade <douglas.b.wade@gmail.com>
* See LICENSE file in root directory for full license.
*/
'use strict'

const utils = require('../utils')
const xnv = require('xml-name-validator')

module.exports = {
meta: {
type: 'problem',
docs: {
description: 'require valid attribute names',
categories: undefined,
url: 'https://eslint.vuejs.org/rules/no-invalid-attribute-name.html'
},
fixable: null,
schema: [],
messages: {
attribute: 'Attribute name {{name}} is not valid.'
}
},
/** @param {RuleContext} context */
create(context) {
/**
* @param {string | VIdentifier} key
* @return {string}
*/
const getName = (key) => (typeof key === 'string' ? key : key.name)

return utils.defineTemplateBodyVisitor(context, {
/** @param {VDirective | VAttribute} node */
VAttribute(node) {
if (utils.isCustomComponent(node.parent.parent)) {
return
}

const name = getName(node.key.name)

if (
node.directive &&
name === 'bind' &&
node.key.argument &&
node.key.argument.type === 'VIdentifier' &&
!xnv.name(node.key.argument.name)
) {
context.report({
node,
messageId: 'attribute',
data: {
name: node.key.argument.name
}
})
}

if (!node.directive && !xnv.name(name)) {
context.report({
node,
messageId: 'attribute',
data: {
name
}
})
}
}
})
}
}
4 changes: 3 additions & 1 deletion package.json
Expand Up @@ -59,14 +59,16 @@
"nth-check": "^2.0.1",
"postcss-selector-parser": "^6.0.9",
"semver": "^7.3.5",
"vue-eslint-parser": "^9.0.1"
"vue-eslint-parser": "^9.0.1",
"xml-name-validator": "^4.0.0"
},
"devDependencies": {
"@types/eslint": "^8.4.2",
"@types/eslint-visitor-keys": "^1.0.0",
"@types/natural-compare": "^1.4.1",
"@types/node": "^13.13.5",
"@types/semver": "^7.3.9",
"@types/xml-name-validator": "^4.0.0",
"@typescript-eslint/parser": "^5.23.0",
"@vuepress/plugin-pwa": "^1.9.7",
"acorn": "^8.7.1",
Expand Down
146 changes: 146 additions & 0 deletions tests/lib/rules/no-invalid-attribute-name.js
@@ -0,0 +1,146 @@
/**
* @author *****your name*****
* See LICENSE file in root directory for full license.
*/
'use strict'

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/no-invalid-attribute-name')

const tester = new RuleTester({
parser: require.resolve('vue-eslint-parser'),
parserOptions: {
ecmaVersion: 2020,
sourceType: 'module'
}
})

tester.run('no-invalid-attribute-name', rule, {
valid: [
{
filename: 'test.vue',
code: '<template><p foo /></template>'
},
{
filename: 'test.vue',
code: `<template><p foo="bar" /></template>`
},
{
filename: 'test.vue',
code: `<template><p foo-bar /></template>`
},
{
filename: 'test.vue',
code: `<template><p _foo-bar /></template>`
},
{
filename: 'test.vue',
code: `<template><p :foo-bar /></template>`
},
{
filename: 'test.vue',
code: `<template><p foo.bar /></template>`
},
{
filename: 'test.vue',
code: `<template><p quux-.9 /></template>`
},
{
filename: 'test.vue',
code: `<template><MyComponent 0abc="foo" /></template>`
},
{
filename: 'test.vue',
code: `<template><MyComponent :0abc="foo" /></template>`
},
{
filename: 'test.vue',
code: `<template><a :href="url"> ... </a></template>`
},
{
filename: 'test.vue',
code: `<template><div v-bind:class="{ active: isActive }"></div></template>`
},
{
filename: 'test.vue',
code: `<template><p v-if="seen">Now you see me</p></template>`
},
{
filename: 'test.vue',
code: `<a v-on:[eventName]="doSomething"> ... </a>`
},
{
filename: 'test.vue',
code: `<form v-on:submit.prevent="onSubmit"> ... </form>`
},
{
filename: 'test.vue',
code: `<a @[event]="doSomething"> ... </a>`
},
{
filename: 'test.vue',
code: `<template><div v-bind="..."></div></template>`
},
{
filename: 'test.vue',
code: `<template><div v-0abc="..."></div></template>`
}
],
invalid: [
{
filename: 'test.vue',
code: `<template><p 0abc /></template>`,
errors: [
{
message: 'Attribute name 0abc is not valid.',
line: 1,
column: 14
}
]
},
{
filename: 'test.vue',
code: `<template><p -def></template>`,
errors: [
{
message: 'Attribute name -def is not valid.',
line: 1,
column: 14
}
]
},
{
filename: 'test.vue',
code: `<template><p !ghi /></template>`,
errors: [
{
message: 'Attribute name !ghi is not valid.',
line: 1,
column: 14
}
]
},
{
filename: 'test.vue',
code: `<template><p v-bind:0abc=""></template>`,
errors: [
{
message: 'Attribute name 0abc is not valid.',
line: 1,
column: 14
}
]
},
{
filename: 'test.vue',
code: `<template><p :0abc="..." /></template>`,
errors: [
{
message: 'Attribute name 0abc is not valid.',
line: 1,
column: 14
}
]
}
]
})

0 comments on commit ab85fd6

Please sign in to comment.