Skip to content

Commit 7681c4b

Browse files
committed
match-component-file-name rule
1 parent 0fd0f7b commit 7681c4b

File tree

6 files changed

+265
-0
lines changed

6 files changed

+265
-0
lines changed

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -224,6 +224,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
224224
|:---|:--------|:------------|
225225
| :wrench: | [vue/attributes-order](./docs/rules/attributes-order.md) | enforce order of attributes |
226226
| :wrench: | [vue/html-quotes](./docs/rules/html-quotes.md) | enforce quotes style of HTML attributes |
227+
| | [vue/match-component-file-name](./docs/rules/match-component-file-name.md) | require component name property to match its file name |
227228
| | [vue/no-v-html](./docs/rules/no-v-html.md) | disallow use of v-html to prevent XSS attack |
228229
| :wrench: | [vue/order-in-components](./docs/rules/order-in-components.md) | enforce order of properties in components |
229230
| | [vue/this-in-template](./docs/rules/this-in-template.md) | enforce usage of `this` in template |
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# require component name property to match its file name (vue/match-component-file-name)
2+
3+
- :gear: This rule is included in `"plugin:vue/recommended"`.
4+
5+
You can choose which file extension this rule should verify the component's name:
6+
7+
- `.jsx` (**default**): `jsx`
8+
- `.jsx` and `.vue`: `both`
9+
10+
By default this rule will only verify components in a file with a `.jsx`
11+
extension. Files with `.vue` extension uses their resgistered name by default.
12+
The only use case where you need to specify a name for your component
13+
in a `.vue` file is when implementing recursive components.
14+
15+
The option to verify both files extensions is added to increase
16+
consistency in projects where its style guide requires every component
17+
to have a `name` property, although, as stated above it is unnecessary.
18+
19+
## :book: Rule Details
20+
21+
This rule reports if a component `name` property does not match its file name.
22+
23+
:-1: Examples of **incorrect** code for this rule:
24+
25+
```jsx
26+
// file name: src/MyComponent.jsx
27+
export default {
28+
name: 'MComponent', // note the missing y
29+
render: () {
30+
return <h1>Hello world</h1>
31+
}
32+
}
33+
```
34+
35+
:+1: Examples of **correct** code for this rule:
36+
37+
```jsx
38+
// file name: src/MyComponent.jsx
39+
export default {
40+
name: 'MyComponent',
41+
render: () {
42+
return <h1>Hello world</h1>
43+
}
44+
}
45+
```
46+
47+
## :wrench: Options
48+
49+
- `"jsx"` (default) ... verify components in files with `.jsx` extension.
50+
- `"both"` ... verify components in files with both `.jsx` and `.vue` extensions.

lib/configs/recommended.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ module.exports = {
88
rules: {
99
'vue/attributes-order': 'error',
1010
'vue/html-quotes': 'error',
11+
'vue/match-component-file-name': 'error',
1112
'vue/no-v-html': 'error',
1213
'vue/order-in-components': 'error',
1314
'vue/this-in-template': 'error'

lib/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ module.exports = {
1818
'html-quotes': require('./rules/html-quotes'),
1919
'html-self-closing': require('./rules/html-self-closing'),
2020
'jsx-uses-vars': require('./rules/jsx-uses-vars'),
21+
'match-component-file-name': require('./rules/match-component-file-name'),
2122
'max-attributes-per-line': require('./rules/max-attributes-per-line'),
2223
'multiline-html-element-content-newline': require('./rules/multiline-html-element-content-newline'),
2324
'mustache-interpolation-spacing': require('./rules/mustache-interpolation-spacing'),
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/**
2+
* @fileoverview Require component name property to match its file name
3+
* @author Rodrigo Pedra Brum <[email protected]>
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
const path = require('path')
13+
14+
// ------------------------------------------------------------------------------
15+
// Rule Definition
16+
// ------------------------------------------------------------------------------
17+
18+
module.exports = {
19+
meta: {
20+
docs: {
21+
description: 'require component name property to match its file name',
22+
category: 'recommended',
23+
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/match-component-file-name.md'
24+
},
25+
fixable: null,
26+
schema: [
27+
{ enum: ['jsx', 'both'] }
28+
]
29+
},
30+
31+
create (context) {
32+
return utils.executeOnVueComponent(context, (object) => {
33+
const nameProperty = object.properties.find((prop) => prop.key.name === 'name')
34+
35+
if (!nameProperty) {
36+
return
37+
}
38+
39+
const allowedExtensions = context.options[0] || 'jsx'
40+
const name = nameProperty.value.value
41+
const [, filename, extension] = /^(.+?)\.(.*)$/g.exec(path.basename(context.getFilename()))
42+
43+
if (extension === 'vue' && allowedExtensions !== 'both') {
44+
return
45+
}
46+
47+
if (name !== filename) {
48+
context.report({
49+
obj: object,
50+
loc: object.loc,
51+
message: 'Component name should match file name ({{filename}} / {{name}}).',
52+
data: { filename, name }
53+
})
54+
}
55+
})
56+
}
57+
}
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
/**
2+
* @fileoverview Require component name property to match its file name
3+
* @author Rodrigo Pedra Brum <[email protected]>
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const RuleTester = require('eslint').RuleTester
12+
const rule = require('../../../lib/rules/match-component-file-name')
13+
14+
const jsxRuleTester = new RuleTester({
15+
parserOptions: {
16+
ecmaVersion: 6,
17+
sourceType: 'module',
18+
ecmaFeatures: {
19+
jsx: true
20+
}
21+
}
22+
})
23+
24+
const vueRuleTester = new RuleTester({
25+
parser: 'vue-eslint-parser',
26+
parserOptions: {
27+
ecmaVersion: 2015,
28+
sourceType: 'module'
29+
}
30+
})
31+
32+
// ------------------------------------------------------------------------------
33+
// Tests
34+
// ------------------------------------------------------------------------------
35+
36+
jsxRuleTester.run('match-component-file-name', rule, {
37+
valid: [
38+
{
39+
filename: 'MyComponent.jsx',
40+
code: `
41+
export default {
42+
name: 'MyComponent',
43+
render() { return <div /> }
44+
}
45+
`
46+
},
47+
{
48+
filename: 'MyComponent.jsx',
49+
code: `
50+
export default {
51+
name: 'MyComponent',
52+
render() { return <div /> }
53+
}
54+
`,
55+
options: ['jsx']
56+
},
57+
{
58+
filename: 'MyComponent.jsx',
59+
code: `
60+
export default {
61+
name: 'MyComponent',
62+
render() { return <div /> }
63+
}
64+
`,
65+
options: ['both']
66+
}
67+
],
68+
69+
invalid: [
70+
{
71+
filename: 'MyComponent.jsx',
72+
code: `
73+
export default {
74+
name: 'MComponent',
75+
render() { return <div /> }
76+
}
77+
`,
78+
errors: [{
79+
message: 'Component name should match file name (MyComponent / MComponent).'
80+
}]
81+
},
82+
{
83+
filename: 'MyComponent.jsx',
84+
code: `
85+
export default {
86+
name: 'MComponent',
87+
render() { return <div /> }
88+
}
89+
`,
90+
options: ['jsx'],
91+
errors: [{
92+
message: 'Component name should match file name (MyComponent / MComponent).'
93+
}]
94+
},
95+
{
96+
filename: 'MyComponent.jsx',
97+
code: `
98+
export default {
99+
name: 'MComponent',
100+
render() { return <div /> }
101+
}
102+
`,
103+
options: ['both'],
104+
errors: [{
105+
message: 'Component name should match file name (MyComponent / MComponent).'
106+
}]
107+
}
108+
]
109+
})
110+
111+
vueRuleTester.run('match-component-file-name', rule, {
112+
valid: [
113+
{
114+
filename: 'MyComponent.vue',
115+
code: `
116+
<script>
117+
export default {
118+
name: 'MComponent',
119+
template: '<div />'
120+
}
121+
</script>
122+
` // missing ["both"] option, so the file is ignored
123+
},
124+
{
125+
filename: 'MyComponent.vue',
126+
code: `
127+
<script>
128+
export default {
129+
name: 'MyComponent',
130+
template: '<div />'
131+
}
132+
</script>
133+
`,
134+
options: ['both']
135+
}
136+
],
137+
138+
invalid: [
139+
{
140+
filename: 'MyComponent.vue',
141+
code: `
142+
<script>
143+
export default {
144+
name: 'MComponent',
145+
template: '<div />'
146+
}
147+
</script>
148+
`,
149+
options: ['both'],
150+
errors: [{
151+
message: 'Component name should match file name (MyComponent / MComponent).'
152+
}]
153+
}
154+
]
155+
})

0 commit comments

Comments
 (0)