Skip to content

⭐️New: Add vue/match-component-file-name rule #668

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 19 commits into from
Nov 24, 2018
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
|:---|:--------|:------------|
| :wrench: | [vue/attributes-order](./docs/rules/attributes-order.md) | enforce order of attributes |
| :wrench: | [vue/html-quotes](./docs/rules/html-quotes.md) | enforce quotes style of HTML attributes |
| | [vue/match-component-file-name](./docs/rules/match-component-file-name.md) | require component name property to match its file name |
| | [vue/no-v-html](./docs/rules/no-v-html.md) | disallow use of v-html to prevent XSS attack |
| :wrench: | [vue/order-in-components](./docs/rules/order-in-components.md) | enforce order of properties in components |
| | [vue/this-in-template](./docs/rules/this-in-template.md) | enforce usage of `this` in template |
Expand Down
50 changes: 50 additions & 0 deletions docs/rules/match-component-file-name.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# require component name property to match its file name (vue/match-component-file-name)

- :gear: This rule is included in `"plugin:vue/recommended"`.

You can choose which file extension this rule should verify the component's name:

- `.jsx` (**default**): `jsx`
- `.jsx` and `.vue`: `both`

By default this rule will only verify components in a file with a `.jsx`
extension. Files with `.vue` extension uses their resgistered name by default.
The only use case where you need to specify a name for your component
in a `.vue` file is when implementing recursive components.

The option to verify both files extensions is added to increase
consistency in projects where its style guide requires every component
to have a `name` property, although, as stated above it is unnecessary.

## :book: Rule Details

This rule reports if a component `name` property does not match its file name.

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

```jsx
// file name: src/MyComponent.jsx
export default {
name: 'MComponent', // note the missing y
render: () {
return <h1>Hello world</h1>
}
}
```

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

```jsx
// file name: src/MyComponent.jsx
export default {
name: 'MyComponent',
render: () {
return <h1>Hello world</h1>
}
}
```

## :wrench: Options

- `"jsx"` (default) ... verify components in files with `.jsx` extension.
- `"both"` ... verify components in files with both `.jsx` and `.vue` extensions.
1 change: 1 addition & 0 deletions lib/configs/recommended.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ module.exports = {
rules: {
'vue/attributes-order': 'error',
'vue/html-quotes': 'error',
'vue/match-component-file-name': 'error',
'vue/no-v-html': 'error',
'vue/order-in-components': 'error',
'vue/this-in-template': 'error'
Expand Down
1 change: 1 addition & 0 deletions lib/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ module.exports = {
'html-quotes': require('./rules/html-quotes'),
'html-self-closing': require('./rules/html-self-closing'),
'jsx-uses-vars': require('./rules/jsx-uses-vars'),
'match-component-file-name': require('./rules/match-component-file-name'),
'max-attributes-per-line': require('./rules/max-attributes-per-line'),
'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
'mustache-interpolation-spacing': require('./rules/mustache-interpolation-spacing'),
Expand Down
57 changes: 57 additions & 0 deletions lib/rules/match-component-file-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* @fileoverview Require component name property to match its file name
* @author Rodrigo Pedra Brum <[email protected]>
*/
'use strict'

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

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

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

module.exports = {
meta: {
docs: {
description: 'require component name property to match its file name',
category: 'recommended',
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/match-component-file-name.md'
},
fixable: null,
schema: [
{ enum: ['jsx', 'both'] }
]
},

create (context) {
return utils.executeOnVueComponent(context, (object) => {
const nameProperty = object.properties.find((prop) => prop.key.name === 'name')

if (!nameProperty) {
return
}

const allowedExtensions = context.options[0] || 'jsx'
const name = nameProperty.value.value
const [, filename, extension] = /^(.+?)\.(.*)$/g.exec(path.basename(context.getFilename()))

if (extension === 'vue' && allowedExtensions !== 'both') {
return
}

if (name !== filename) {
context.report({
obj: object,
loc: object.loc,
message: 'Component name should match file name ({{filename}} / {{name}}).',
data: { filename, name }
})
}
})
}
}
155 changes: 155 additions & 0 deletions tests/lib/rules/match-component-file-name.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
/**
* @fileoverview Require component name property to match its file name
* @author Rodrigo Pedra Brum <[email protected]>
*/
'use strict'

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

const RuleTester = require('eslint').RuleTester
const rule = require('../../../lib/rules/match-component-file-name')

const jsxRuleTester = new RuleTester({
parserOptions: {
ecmaVersion: 6,
sourceType: 'module',
ecmaFeatures: {
jsx: true
}
}
})

const vueRuleTester = new RuleTester({
parser: 'vue-eslint-parser',
parserOptions: {
ecmaVersion: 2015,
sourceType: 'module'
}
})

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

jsxRuleTester.run('match-component-file-name', rule, {
valid: [
{
filename: 'MyComponent.jsx',
code: `
export default {
name: 'MyComponent',
render() { return <div /> }
}
`
},
{
filename: 'MyComponent.jsx',
code: `
export default {
name: 'MyComponent',
render() { return <div /> }
}
`,
options: ['jsx']
},
{
filename: 'MyComponent.jsx',
code: `
export default {
name: 'MyComponent',
render() { return <div /> }
}
`,
options: ['both']
}
],

invalid: [
{
filename: 'MyComponent.jsx',
code: `
export default {
name: 'MComponent',
render() { return <div /> }
}
`,
errors: [{
message: 'Component name should match file name (MyComponent / MComponent).'
}]
},
{
filename: 'MyComponent.jsx',
code: `
export default {
name: 'MComponent',
render() { return <div /> }
}
`,
options: ['jsx'],
errors: [{
message: 'Component name should match file name (MyComponent / MComponent).'
}]
},
{
filename: 'MyComponent.jsx',
code: `
export default {
name: 'MComponent',
render() { return <div /> }
}
`,
options: ['both'],
errors: [{
message: 'Component name should match file name (MyComponent / MComponent).'
}]
}
]
})

vueRuleTester.run('match-component-file-name', rule, {
valid: [
{
filename: 'MyComponent.vue',
code: `
<script>
export default {
name: 'MComponent',
template: '<div />'
}
</script>
` // missing ["both"] option, so the file is ignored
},
{
filename: 'MyComponent.vue',
code: `
<script>
export default {
name: 'MyComponent',
template: '<div />'
}
</script>
`,
options: ['both']
}
],

invalid: [
{
filename: 'MyComponent.vue',
code: `
<script>
export default {
name: 'MComponent',
template: '<div />'
}
</script>
`,
options: ['both'],
errors: [{
message: 'Component name should match file name (MyComponent / MComponent).'
}]
}
]
})