Skip to content

Commit e887a8d

Browse files
ymichalsnik
y
authored andcommitted
[New] Add prop-name-casing
1 parent 733172c commit e887a8d

File tree

5 files changed

+310
-0
lines changed

5 files changed

+310
-0
lines changed

docs/rules/prop-name-casing.md

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
# enforce specific casing for the Prop name in Vue components(prop-name-casing)
2+
3+
This rule would enforce proper casing of props in vue components(camelCase).
4+
5+
## :book: Rule Details
6+
7+
(https://vuejs.org/v2/style-guide/#Prop-name-casing-strongly-recommended).
8+
9+
:+1: Examples of **correct** code for `camelCase`:
10+
11+
```js
12+
export default {
13+
props: {
14+
greetingText: String
15+
}
16+
}
17+
```
18+
19+
:-1: Examples of **incorrect** code for `camelCase`:
20+
21+
```js
22+
export default {
23+
props: {
24+
'greeting-text': String
25+
}
26+
}
27+
```
28+
29+
## :wrench: Options
30+
31+
Default casing is set to `camelCase`.
32+
33+
```
34+
"vue/prop-name-casing": ["error", "camelCase|snake_case"]
35+
```

lib/rules/prop-name-casing.js

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
/**
2+
* @fileoverview Requires specific casing for the Prop name in Vue components
3+
* @author Yu Kimura
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
const casing = require('../utils/casing')
9+
const allowedCaseOptions = ['camelCase', 'snake_case']
10+
11+
// ------------------------------------------------------------------------------
12+
// Rule Definition
13+
// ------------------------------------------------------------------------------
14+
15+
function create (context) {
16+
const options = context.options[0]
17+
const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'camelCase'
18+
const converter = casing.getConverter(caseType)
19+
20+
// ----------------------------------------------------------------------
21+
// Public
22+
// ----------------------------------------------------------------------
23+
24+
return utils.executeOnVue(context, (obj) => {
25+
const node = obj.properties.find(p =>
26+
p.type === 'Property' &&
27+
p.key.type === 'Identifier' &&
28+
p.key.name === 'props'
29+
)
30+
if (!node) return
31+
32+
const items = node.value.type === 'ObjectExpression' ? node.value.properties : node.value.elements
33+
for (const item of items) {
34+
if (item.type !== 'Property') {
35+
return
36+
}
37+
38+
const propName = item.key.type === 'Literal' ? item.key.value : item.key.name
39+
const convertedName = converter(propName)
40+
if (convertedName !== propName) {
41+
context.report({
42+
node: item,
43+
message: 'Prop "{{name}}" is not {{caseType}}.',
44+
data: {
45+
name: propName,
46+
caseType: caseType
47+
}
48+
})
49+
}
50+
}
51+
})
52+
}
53+
54+
// ------------------------------------------------------------------------------
55+
// Rule Definition
56+
// ------------------------------------------------------------------------------
57+
58+
module.exports = {
59+
meta: {
60+
docs: {
61+
description: 'enforce specific casing for the Prop name in Vue components',
62+
category: 'strongly-recommended'
63+
},
64+
fixable: null, // or "code" or "whitespace"
65+
schema: [
66+
{
67+
enum: allowedCaseOptions
68+
}
69+
]
70+
},
71+
72+
create
73+
}

lib/utils/casing.js

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,18 @@ function kebabCase (str) {
1414
.toLowerCase()
1515
}
1616

17+
/**
18+
* Convert text to snake_case
19+
* @param {string} str Text to be converted
20+
* @return {string}
21+
*/
22+
function snakeCase (str) {
23+
return str
24+
.replace(/([a-z])([A-Z])/g, match => match[0] + '_' + match[1])
25+
.replace(invalidChars, '_')
26+
.toLowerCase()
27+
}
28+
1729
/**
1830
* Convert text to camelCase
1931
* @param {string} str Text to be converted
@@ -42,6 +54,7 @@ function pascalCase (str) {
4254

4355
const convertersMap = {
4456
'kebab-case': kebabCase,
57+
'snake_case': snakeCase,
4558
'camelCase': camelCase,
4659
'PascalCase': pascalCase
4760
}

tests/lib/rules/prop-name-casing.js

Lines changed: 180 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,180 @@
1+
/**
2+
* @fileoverview Define a style for the name property casing for consistency purposes
3+
* @author Yu Kimura
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/prop-name-casing')
12+
const RuleTester = require('eslint').RuleTester
13+
14+
// ------------------------------------------------------------------------------
15+
// Tests
16+
// ------------------------------------------------------------------------------
17+
18+
const parserOptions = {
19+
ecmaVersion: 6,
20+
sourceType: 'module',
21+
ecmaFeatures: { experimentalObjectRestSpread: true }
22+
}
23+
24+
const ruleTester = new RuleTester()
25+
ruleTester.run('prop-name-casing', rule, {
26+
27+
valid: [
28+
{
29+
filename: 'test.vue',
30+
code: `
31+
export default {
32+
props: ['greetingText']
33+
}
34+
`,
35+
parserOptions
36+
},
37+
{
38+
filename: 'test.vue',
39+
code: `
40+
export default {
41+
props: ['greetingText']
42+
}
43+
`,
44+
options: ['camelCase'],
45+
parserOptions
46+
},
47+
{
48+
filename: 'test.vue',
49+
code: `
50+
export default {
51+
props: ['greetingText']
52+
}
53+
`,
54+
options: ['snake_case'],
55+
parserOptions
56+
},
57+
{
58+
filename: 'test.vue',
59+
code: `
60+
export default {
61+
props: {
62+
greetingText: String
63+
}
64+
}
65+
`,
66+
parserOptions
67+
},
68+
{
69+
filename: 'test.vue',
70+
code: `
71+
export default {
72+
props: {
73+
greetingText: String
74+
}
75+
}
76+
`,
77+
options: ['camelCase'],
78+
parserOptions
79+
},
80+
{
81+
filename: 'test.vue',
82+
code: `
83+
export default {
84+
props: {
85+
greeting_text: String
86+
}
87+
}
88+
`,
89+
options: ['snake_case'],
90+
parserOptions
91+
}
92+
],
93+
94+
invalid: [
95+
{
96+
filename: 'test.vue',
97+
code: `
98+
export default {
99+
props: {
100+
greeting_text: String
101+
}
102+
}
103+
`,
104+
parserOptions,
105+
errors: [{
106+
message: 'Prop "greeting_text" is not camelCase.',
107+
type: 'Property',
108+
line: 4
109+
}]
110+
},
111+
{
112+
filename: 'test.vue',
113+
code: `
114+
export default {
115+
props: {
116+
greeting_text: String
117+
}
118+
}
119+
`,
120+
options: ['camelCase'],
121+
parserOptions,
122+
errors: [{
123+
message: 'Prop "greeting_text" is not camelCase.',
124+
type: 'Property',
125+
line: 4
126+
}]
127+
},
128+
{
129+
filename: 'test.vue',
130+
code: `
131+
export default {
132+
props: {
133+
greetingText: String
134+
}
135+
}
136+
`,
137+
options: ['snake_case'],
138+
parserOptions,
139+
errors: [{
140+
message: 'Prop "greetingText" is not snake_case.',
141+
type: 'Property',
142+
line: 4
143+
}]
144+
},
145+
{
146+
filename: 'test.vue',
147+
code: `
148+
export default {
149+
props: {
150+
'greeting-text': String
151+
}
152+
}
153+
`,
154+
options: ['camelCase'],
155+
parserOptions,
156+
errors: [{
157+
message: 'Prop "greeting-text" is not camelCase.',
158+
type: 'Property',
159+
line: 4
160+
}]
161+
},
162+
{
163+
filename: 'test.vue',
164+
code: `
165+
export default {
166+
props: {
167+
'greeting-text': String
168+
}
169+
}
170+
`,
171+
options: ['snake_case'],
172+
parserOptions,
173+
errors: [{
174+
message: 'Prop "greeting-text" is not snake_case.',
175+
type: 'Property',
176+
line: 4
177+
}]
178+
}
179+
]
180+
})

tests/lib/utils/casing.js

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,4 +32,13 @@ describe('getConverter()', () => {
3232
assert.ok(converter('FooBar') === 'foo-bar')
3333
assert.ok(converter('Foo1Bar') === 'foo1bar')
3434
})
35+
36+
it('should conver string to snake_case', () => {
37+
const converter = casing.getConverter('snake_case')
38+
39+
assert.ok(converter('fooBar') === 'foo_bar')
40+
assert.ok(converter('foo-bar') === 'foo_bar')
41+
assert.ok(converter('FooBar') === 'foo_bar')
42+
assert.ok(converter('Foo1Bar') === 'foo1bar')
43+
})
3544
})

0 commit comments

Comments
 (0)