Skip to content

Commit

Permalink
Add rule html-attributes-casing.
Browse files Browse the repository at this point in the history
  • Loading branch information
armano2 committed Jul 24, 2017
1 parent 3361366 commit 50dce57
Show file tree
Hide file tree
Showing 7 changed files with 535 additions and 27 deletions.
35 changes: 35 additions & 0 deletions docs/rules/html-attributes-casing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# Define a style for the attributes casing in templates. (html-attributes-casing)

Define a style for the attributes casing in templates.

:+1: Examples of **correct** code for `PascalCase`:

```html
<template>
<component MyProp="prop"></component>
</template>
```

:+1: Examples of **correct** code for `kebab-case`:

```html
<template>
<component my-prop="prop"></component>
</template>
```

:+1: Examples of **correct** code for `camelCase`:

```html
<template>
<component myProp="prop"></component>
</template>
```

## :wrench: Options

Default casing is set to `kebab-case`

```
'vue/html-attributes-casing': [2, 'camelCase'|'kebab-case'|'PascalCase']
```
85 changes: 85 additions & 0 deletions lib/rules/html-attributes-casing.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
/**
* @fileoverview Define a style for the props casing in templates.
* @author Armano
*/
'use strict'

const utils = require('../utils')
const casing = require('../utils/casing')

// ------------------------------------------------------------------------------
// Rule Definition
// ------------------------------------------------------------------------------

function create (context) {
const sourceCode = context.getSourceCode()
const options = context.options[0]
const caseType = casing.allowedCaseOptions.indexOf(options) !== -1 ? options : 'kebab-case'

function reportIssue (node, name, newName) {
context.report({
node: node.key,
loc: node.loc,
message: "Attribute '{{name}}' is not {{caseType}}.",
data: {
name,
caseType,
newName
},
fix: fixer => fixer.replaceText(node.key, newName)
})
}

// ----------------------------------------------------------------------
// Public
// ----------------------------------------------------------------------

utils.registerTemplateBodyVisitor(context, {
'VStartTag' (obj) {
if (!utils.isSvgElementName(obj.id.name) && !utils.isMathMLElementName(obj.id.name)) {
obj.attributes.forEach((node) => {
if (!node.directive) {
const oldValue = node.key.name
if (oldValue.indexOf('data-') !== -1) {
return
}
const value = casing.getConverter(caseType)(oldValue)
if (value !== oldValue) {
reportIssue(node, oldValue, value)
}
} else if (node.key.name === 'bind') {
const oldValue = node.key.argument
if (oldValue.indexOf('data-') !== -1) {
return
}
const text = sourceCode.getText(node.key)
const value = casing.getConverter(caseType)(oldValue)
if (value !== oldValue) {
reportIssue(node, text, text.replace(oldValue, value))
}
}
})
}
}
})

return {}
}

module.exports = {
meta: {
docs: {
description: 'Define a style for the props casing in templates.',
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code',
schema: [
{
enum: casing.allowedCaseOptions
}
]
},

create
}
2 changes: 1 addition & 1 deletion lib/rules/html-no-self-closing.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const utils = require('../utils')
function create (context) {
utils.registerTemplateBodyVisitor(context, {
'VStartTag[selfClosing=true]' (node) {
if (!utils.isSvgElementName(node.id.name)) {
if (!utils.isSvgElementName(node.id.name) && !utils.isMathMLElementName(node.id.name)) {
const pos = node.range[1] - 2
context.report({
node,
Expand Down
2 changes: 1 addition & 1 deletion lib/rules/name-property-casing.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ module.exports = {
category: 'Stylistic Issues',
recommended: false
},
fixable: 'code', // or "code" or "whitespace"
fixable: 'code',
schema: [
{
enum: casing.allowedCaseOptions
Expand Down
62 changes: 37 additions & 25 deletions lib/utils/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

const HTML_ELEMENT_NAMES = new Set(require('./html-elements.json'))
const SVG_ELEMENT_NAMES = new Set(require('./svg-elements.json'))
const MATHML_ELEMENT_NAMES = new Set(require('./mathml-elements.json'))
const VOID_ELEMENT_NAMES = new Set(require('./void-elements.json'))
const assert = require('assert')

Expand Down Expand Up @@ -67,8 +68,8 @@ module.exports = {
assert(node && node.type === 'VElement')

return (
node.parent.type === 'Program' ||
node.parent.parent.type === 'Program'
node.parent.type === 'Program' ||
node.parent.parent.type === 'Program'
)
},

Expand Down Expand Up @@ -194,49 +195,60 @@ module.exports = {
)
},

/**
* Check whether the given node is a custom component or not.
* @param {ASTNode} node The start tag node to check.
* @returns {boolean} `true` if the node is a custom component.
*/
/**
* Check whether the given node is a custom component or not.
* @param {ASTNode} node The start tag node to check.
* @returns {boolean} `true` if the node is a custom component.
*/
isCustomComponent (node) {
assert(node && node.type === 'VStartTag')

const name = node.id.name
return (
!(this.isHtmlElementName(name) || this.isSvgElementName(name)) ||
this.hasAttribute(node, 'is') ||
this.hasDirective(node, 'bind', 'is')
!(this.isHtmlElementName(name) || this.isSvgElementName(name) || this.isMathMLElementName(name)) ||
this.hasAttribute(node, 'is') ||
this.hasDirective(node, 'bind', 'is')
)
},

/**
* Check whether the given name is a HTML element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a HTML element name.
*/
/**
* Check whether the given name is a HTML element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a HTML element name.
*/
isHtmlElementName (name) {
assert(typeof name === 'string')

return HTML_ELEMENT_NAMES.has(name.toLowerCase())
},

/**
* Check whether the given name is a SVG element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a SVG element name.
*/
/**
* Check whether the given name is a SVG element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a SVG element name.
*/
isSvgElementName (name) {
assert(typeof name === 'string')

return SVG_ELEMENT_NAMES.has(name.toLowerCase())
},

/**
* Check whether the given name is a void element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a void element name.
*/
/**
* Check whether the given name is a MathML element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a HTML element name.
*/
isMathMLElementName (name) {
assert(typeof name === 'string')

return MATHML_ELEMENT_NAMES.has(name.toLowerCase())
},

/**
* Check whether the given name is a void element name or not.
* @param {string} name The name to check.
* @returns {boolean} `true` if the name is a void element name.
*/
isVoidElementName (name) {
assert(typeof name === 'string')

Expand Down
Loading

0 comments on commit 50dce57

Please sign in to comment.