Skip to content

Commit e1ad64d

Browse files
committed
Add rule html-attributes-casing.
1 parent 29d1cb6 commit e1ad64d

File tree

3 files changed

+256
-0
lines changed

3 files changed

+256
-0
lines changed

docs/rules/html-attributes-casing.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# Define a style for the attributes casing in templates. (html-attributes-casing)
2+
3+
Define a style for the attributes casing in templates.
4+
5+
:+1: Examples of **correct** code for `PascalCase`:
6+
7+
```html
8+
<template>
9+
<component MyProp="prop"></component>
10+
</template>
11+
```
12+
13+
:+1: Examples of **correct** code for `kebab-case`:
14+
15+
```html
16+
<template>
17+
<component my-prop="prop"></component>
18+
</template>
19+
```
20+
21+
:+1: Examples of **correct** code for `camelCase`:
22+
23+
```html
24+
<template>
25+
<component myProp="prop"></component>
26+
</template>
27+
```
28+
29+
## :wrench: Options
30+
31+
Default casing is set to `kebab-case`
32+
33+
```
34+
'vue/html-attributes-casing': [2, 'camelCase'|'kebab-case'|'PascalCase']
35+
```

lib/rules/html-attributes-casing.js

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/**
2+
* @fileoverview Define a style for the props casing in templates.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
function kebabCase (str) {
10+
return str.replace(/([a-z])([A-Z])/g, match => match[0] + '-' + match[1]).replace(/\s+/g, '-').toLowerCase()
11+
}
12+
13+
function camelCase (str) {
14+
return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => index === 0 ? letter.toLowerCase() : letter.toUpperCase()).replace(/[\s-]+/g, '')
15+
}
16+
17+
function pascalCase (str) {
18+
str = camelCase(str)
19+
return str.length > 0 ? str.charAt(0).toUpperCase() + str.slice(1) : ''
20+
}
21+
22+
function convertCase (str, caseType) {
23+
if (caseType === 'kebab-case') {
24+
return kebabCase(str)
25+
} else if (caseType === 'PascalCase') {
26+
return pascalCase(str)
27+
}
28+
return camelCase(str)
29+
}
30+
31+
// ------------------------------------------------------------------------------
32+
// Rule Definition
33+
// ------------------------------------------------------------------------------
34+
35+
function create (context) {
36+
const options = context.options[0]
37+
const caseType = ['camelCase', 'kebab-case', 'PascalCase'].indexOf(options) !== -1 ? options : 'kebab-case'
38+
39+
// ----------------------------------------------------------------------
40+
// Public
41+
// ----------------------------------------------------------------------
42+
43+
utils.registerTemplateBodyVisitor(context, {
44+
"VAttribute[directive=true][key.name='bind']" (node) {
45+
const value = convertCase(node.key.argument, caseType)
46+
if (value !== node.key.argument) {
47+
context.report({
48+
node: node.key,
49+
loc: node.loc,
50+
message: 'Attribute {{bind}}:{{name}} is not {{caseType}}.',
51+
data: {
52+
name: node.key.argument,
53+
caseType: caseType,
54+
bind: node.key.shorthand ? '' : 'v-bind'
55+
}
56+
})
57+
}
58+
},
59+
'VAttribute[directive=false]' (node) {
60+
if (node.key.type === 'VIdentifier') {
61+
const value = convertCase(node.key.name, caseType)
62+
if (value !== node.key.name) {
63+
context.report({
64+
node: node.key,
65+
loc: node.loc,
66+
message: 'Attribute {{name}} is not {{caseType}}.',
67+
data: {
68+
name: node.key.name,
69+
caseType: caseType
70+
}
71+
})
72+
}
73+
}
74+
}
75+
})
76+
77+
return {}
78+
}
79+
80+
module.exports = {
81+
meta: {
82+
docs: {
83+
description: 'Define a style for the props casing in templates.',
84+
category: 'Stylistic Issues',
85+
recommended: false
86+
},
87+
fixable: null, // or "code" or "whitespace"
88+
schema: [
89+
{
90+
enum: ['camelCase', 'kebab-case', 'PascalCase']
91+
}
92+
]
93+
},
94+
95+
create
96+
}
Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
/**
2+
* @fileoverview Define a style for the props casing in templates.
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/html-attributes-casing')
12+
13+
const RuleTester = require('eslint').RuleTester
14+
15+
// ------------------------------------------------------------------------------
16+
// Tests
17+
// ------------------------------------------------------------------------------
18+
19+
const ruleTester = new RuleTester({
20+
parser: 'vue-eslint-parser',
21+
parserOptions: { ecmaVersion: 2015 }
22+
})
23+
24+
ruleTester.run('html-attributes-casing', rule, {
25+
26+
valid: [
27+
{
28+
filename: 'test.vue',
29+
code: ''
30+
},
31+
{
32+
filename: 'test.vue',
33+
code: '<template><div><component my-prop="prop"></component></div></template>',
34+
options: ['kebab-case']
35+
},
36+
{
37+
filename: 'test.vue',
38+
code: '<template><div><component myProp="prop"></component></div></template>',
39+
options: ['camelCase']
40+
},
41+
{
42+
filename: 'test.vue',
43+
code: '<template><div><component MyProp="prop"></component></div></template>',
44+
options: ['PascalCase']
45+
},
46+
{
47+
filename: 'test.vue',
48+
code: '<template><div><component :my-prop="prop"></component></div></template>',
49+
options: ['kebab-case']
50+
},
51+
{
52+
filename: 'test.vue',
53+
code: '<template><div><component :myProp="prop"></component></div></template>',
54+
options: ['camelCase']
55+
},
56+
{
57+
filename: 'test.vue',
58+
code: '<template><div><component :MyProp="prop"></component></div></template>',
59+
options: ['PascalCase']
60+
}
61+
],
62+
63+
invalid: [
64+
{
65+
filename: 'test.vue',
66+
code: '<template><div><component my-prop="prop"></component></div></template>',
67+
options: ['camelCase'],
68+
errors: [{
69+
message: 'Attribute my-prop is not camelCase.',
70+
type: 'VIdentifier',
71+
line: 1
72+
}]
73+
},
74+
{
75+
filename: 'test.vue',
76+
code: '<template><div><component my-prop="prop"></component></div></template>',
77+
options: ['PascalCase'],
78+
errors: [{
79+
message: 'Attribute my-prop is not PascalCase.',
80+
type: 'VIdentifier',
81+
line: 1
82+
}]
83+
},
84+
{
85+
filename: 'test.vue',
86+
code: '<template><div><component MyProp="prop"></component></div></template>',
87+
options: ['kebab-case'],
88+
errors: [{
89+
message: 'Attribute MyProp is not kebab-case.',
90+
type: 'VIdentifier',
91+
line: 1
92+
}]
93+
},
94+
{
95+
filename: 'test.vue',
96+
code: '<template><div><component :my-prop="prop"></component></div></template>',
97+
options: ['camelCase'],
98+
errors: [{
99+
message: 'Attribute :my-prop is not camelCase.',
100+
type: 'VDirectiveKey',
101+
line: 1
102+
}]
103+
},
104+
{
105+
filename: 'test.vue',
106+
code: '<template><div><component :my-prop="prop"></component></div></template>',
107+
options: ['PascalCase'],
108+
errors: [{
109+
message: 'Attribute :my-prop is not PascalCase.',
110+
type: 'VDirectiveKey',
111+
line: 1
112+
}]
113+
},
114+
{
115+
filename: 'test.vue',
116+
code: '<template><div><component :MyProp="prop"></component></div></template>',
117+
options: ['kebab-case'],
118+
errors: [{
119+
message: 'Attribute :MyProp is not kebab-case.',
120+
type: 'VDirectiveKey',
121+
line: 1
122+
}]
123+
}
124+
]
125+
})

0 commit comments

Comments
 (0)