Skip to content

Commit

Permalink
Add prop-specificity rule.
Browse files Browse the repository at this point in the history
  • Loading branch information
armano2 committed Jul 19, 2017
1 parent 1d00dd6 commit 71e583e
Show file tree
Hide file tree
Showing 3 changed files with 270 additions and 0 deletions.
42 changes: 42 additions & 0 deletions docs/rules/prop-specificity.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Prop definitions should be detailed (prop-specificity)

In committed code, prop definitions should always be as detailed as possible, specifying at least type(s).

## :book: Rule Details

This rule enforces that a `props` statement contains type definition.

:-1: Examples of **incorrect** code for this rule:

```js
export default {
props: ['status']
}
```

:+1: Examples of **correct** code for this rule:

```js
export default {
props: {
status: String
}
}
```

```js
export default {
props: {
status: {
type: String,
required: true,
validate: function (value) {
return ['syncing', 'synced', 'version-conflict', 'error'].indexOf(value) !== -1
}
}
}
}
```
## :wrench: Options

Nothing.
107 changes: 107 additions & 0 deletions lib/rules/prop-specificity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/**
* @fileoverview Prop definitions should be detailed
* @author Armano
*/
'use strict'

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

function create (context) {
// variables should be defined here

// ----------------------------------------------------------------------
// Helpers
// ----------------------------------------------------------------------

function getPropTypes (componentProperties) {
const node = componentProperties
.filter(p =>
p.key.type === 'Identifier' &&
p.key.name === 'props'
)[0]

if (!node) {
return {
type: null,
nodes: []
}
}

let nodes = []
const type = node.value.type
if (type === 'ObjectExpression') { // props: {
nodes = node.value.properties.map(cp => {
const key = cp.key.name
let hasType = true
if (cp.value.type === 'ObjectExpression') { // foo: {
hasType = !!(cp.value.properties
.filter(p =>
p.key.type === 'Identifier' &&
p.key.name === 'type' &&
(
p.value.type !== 'ArrayExpression' ||
p.value.elements.length > 0
)
)[0])
} else if (cp.value.type === 'ArrayExpression') { // foo: [
hasType = cp.value.elements.length > 0
}
return { key, node: cp, hasType }
})
} else if (type === 'ArrayExpression') { // props: [
// nodes = node.elements
}

return {
type,
node,
nodes
}
}

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

return utils.executeOnVueComponent(context, (obj) => {
const data = getPropTypes(obj.properties)
if (data.type === 'ObjectExpression') {
data.nodes.forEach(cp => {
if (!cp.hasType) {
context.report({
node: cp.node || data.node,
message: 'Prop "{{name}}" definitions should always be as detailed with at least type(s).',
data: {
name: cp.key
}
})
}
})
} else if (data.type === 'ArrayExpression') {
context.report({
node: data.node,
message: 'Props definitions should always be an object and detailed with at least type(s).'
})
}
})
}

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

module.exports = {
meta: {
docs: {
description: 'Prop definitions should be detailed',
category: 'Best Practices',
recommended: false
},
fixable: null, // or "code" or "whitespace"
schema: [
// fill in your schema
]
},

create
}
121 changes: 121 additions & 0 deletions tests/lib/rules/prop-specificity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
/**
* @fileoverview Prop definitions should be detailed
* @author Armano
*/
'use strict'

// ------------------------------------------------------------------------------
// Requirements
// ------------------------------------------------------------------------------

const rule = require('../../../lib/rules/prop-specificity')

const RuleTester = require('eslint').RuleTester

// ------------------------------------------------------------------------------
// Tests
// ------------------------------------------------------------------------------

var ruleTester = new RuleTester()
ruleTester.run('prop-specificity', rule, {

valid: [
{
filename: 'test.vue',
code: `
export default {
props: {
foo: String
}
}
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
},
{
filename: 'test.vue',
code: `
export default {
props: {
foo: [String, Number]
}
}
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
},
{
filename: 'test.vue',
code: `
export default {
props: {
foo: {
type: String
}
}
}
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' }
}
],

invalid: [
{
filename: 'test.vue',
code: `
export default {
props: ['foo']
}
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
errors: [{
message: 'Props definitions should always be an object and detailed with at least type(s).',
line: 3
}]
},
{
filename: 'test.js',
code: `
new Vue({
props: ['foo']
})
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
errors: [{
message: 'Props definitions should always be an object and detailed with at least type(s).',
line: 3
}]
},
{
filename: 'test.vue',
code: `
export default {
props: {
foo: {
}
}
}
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
errors: [{
message: 'Prop "foo" definitions should always be as detailed with at least type(s).',
line: 4
}]
},
{
filename: 'test.vue',
code: `
export default {
props: {
foo: {
type: []
}
}
}
`,
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
errors: [{
message: 'Prop "foo" definitions should always be as detailed with at least type(s).',
line: 4
}]
}
]
})

0 comments on commit 71e583e

Please sign in to comment.