Skip to content

Commit 71e583e

Browse files
committed
Add prop-specificity rule.
1 parent 1d00dd6 commit 71e583e

File tree

3 files changed

+270
-0
lines changed

3 files changed

+270
-0
lines changed

docs/rules/prop-specificity.md

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
# Prop definitions should be detailed (prop-specificity)
2+
3+
In committed code, prop definitions should always be as detailed as possible, specifying at least type(s).
4+
5+
## :book: Rule Details
6+
7+
This rule enforces that a `props` statement contains type definition.
8+
9+
:-1: Examples of **incorrect** code for this rule:
10+
11+
```js
12+
export default {
13+
props: ['status']
14+
}
15+
```
16+
17+
:+1: Examples of **correct** code for this rule:
18+
19+
```js
20+
export default {
21+
props: {
22+
status: String
23+
}
24+
}
25+
```
26+
27+
```js
28+
export default {
29+
props: {
30+
status: {
31+
type: String,
32+
required: true,
33+
validate: function (value) {
34+
return ['syncing', 'synced', 'version-conflict', 'error'].indexOf(value) !== -1
35+
}
36+
}
37+
}
38+
}
39+
```
40+
## :wrench: Options
41+
42+
Nothing.

lib/rules/prop-specificity.js

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
/**
2+
* @fileoverview Prop definitions should be detailed
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
const utils = require('../utils')
8+
9+
function create (context) {
10+
// variables should be defined here
11+
12+
// ----------------------------------------------------------------------
13+
// Helpers
14+
// ----------------------------------------------------------------------
15+
16+
function getPropTypes (componentProperties) {
17+
const node = componentProperties
18+
.filter(p =>
19+
p.key.type === 'Identifier' &&
20+
p.key.name === 'props'
21+
)[0]
22+
23+
if (!node) {
24+
return {
25+
type: null,
26+
nodes: []
27+
}
28+
}
29+
30+
let nodes = []
31+
const type = node.value.type
32+
if (type === 'ObjectExpression') { // props: {
33+
nodes = node.value.properties.map(cp => {
34+
const key = cp.key.name
35+
let hasType = true
36+
if (cp.value.type === 'ObjectExpression') { // foo: {
37+
hasType = !!(cp.value.properties
38+
.filter(p =>
39+
p.key.type === 'Identifier' &&
40+
p.key.name === 'type' &&
41+
(
42+
p.value.type !== 'ArrayExpression' ||
43+
p.value.elements.length > 0
44+
)
45+
)[0])
46+
} else if (cp.value.type === 'ArrayExpression') { // foo: [
47+
hasType = cp.value.elements.length > 0
48+
}
49+
return { key, node: cp, hasType }
50+
})
51+
} else if (type === 'ArrayExpression') { // props: [
52+
// nodes = node.elements
53+
}
54+
55+
return {
56+
type,
57+
node,
58+
nodes
59+
}
60+
}
61+
62+
// ----------------------------------------------------------------------
63+
// Public
64+
// ----------------------------------------------------------------------
65+
66+
return utils.executeOnVueComponent(context, (obj) => {
67+
const data = getPropTypes(obj.properties)
68+
if (data.type === 'ObjectExpression') {
69+
data.nodes.forEach(cp => {
70+
if (!cp.hasType) {
71+
context.report({
72+
node: cp.node || data.node,
73+
message: 'Prop "{{name}}" definitions should always be as detailed with at least type(s).',
74+
data: {
75+
name: cp.key
76+
}
77+
})
78+
}
79+
})
80+
} else if (data.type === 'ArrayExpression') {
81+
context.report({
82+
node: data.node,
83+
message: 'Props definitions should always be an object and detailed with at least type(s).'
84+
})
85+
}
86+
})
87+
}
88+
89+
// ------------------------------------------------------------------------------
90+
// Rule Definition
91+
// ------------------------------------------------------------------------------
92+
93+
module.exports = {
94+
meta: {
95+
docs: {
96+
description: 'Prop definitions should be detailed',
97+
category: 'Best Practices',
98+
recommended: false
99+
},
100+
fixable: null, // or "code" or "whitespace"
101+
schema: [
102+
// fill in your schema
103+
]
104+
},
105+
106+
create
107+
}

tests/lib/rules/prop-specificity.js

+121
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/**
2+
* @fileoverview Prop definitions should be detailed
3+
* @author Armano
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const rule = require('../../../lib/rules/prop-specificity')
12+
13+
const RuleTester = require('eslint').RuleTester
14+
15+
// ------------------------------------------------------------------------------
16+
// Tests
17+
// ------------------------------------------------------------------------------
18+
19+
var ruleTester = new RuleTester()
20+
ruleTester.run('prop-specificity', rule, {
21+
22+
valid: [
23+
{
24+
filename: 'test.vue',
25+
code: `
26+
export default {
27+
props: {
28+
foo: String
29+
}
30+
}
31+
`,
32+
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
33+
},
34+
{
35+
filename: 'test.vue',
36+
code: `
37+
export default {
38+
props: {
39+
foo: [String, Number]
40+
}
41+
}
42+
`,
43+
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
44+
},
45+
{
46+
filename: 'test.vue',
47+
code: `
48+
export default {
49+
props: {
50+
foo: {
51+
type: String
52+
}
53+
}
54+
}
55+
`,
56+
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
57+
}
58+
],
59+
60+
invalid: [
61+
{
62+
filename: 'test.vue',
63+
code: `
64+
export default {
65+
props: ['foo']
66+
}
67+
`,
68+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
69+
errors: [{
70+
message: 'Props definitions should always be an object and detailed with at least type(s).',
71+
line: 3
72+
}]
73+
},
74+
{
75+
filename: 'test.js',
76+
code: `
77+
new Vue({
78+
props: ['foo']
79+
})
80+
`,
81+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
82+
errors: [{
83+
message: 'Props definitions should always be an object and detailed with at least type(s).',
84+
line: 3
85+
}]
86+
},
87+
{
88+
filename: 'test.vue',
89+
code: `
90+
export default {
91+
props: {
92+
foo: {
93+
}
94+
}
95+
}
96+
`,
97+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
98+
errors: [{
99+
message: 'Prop "foo" definitions should always be as detailed with at least type(s).',
100+
line: 4
101+
}]
102+
},
103+
{
104+
filename: 'test.vue',
105+
code: `
106+
export default {
107+
props: {
108+
foo: {
109+
type: []
110+
}
111+
}
112+
}
113+
`,
114+
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
115+
errors: [{
116+
message: 'Prop "foo" definitions should always be as detailed with at least type(s).',
117+
line: 4
118+
}]
119+
}
120+
]
121+
})

0 commit comments

Comments
 (0)