Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit c6bbd95

Browse files
rodrigopedraota-meshi
authored andcommitted
⭐️New: Add vue/match-component-file-name rule (#668)
* match-component-file-name rule * add tests when there is no name attribute and improve error message * apply suggestions from @armano2 * refactor to have file extensions in options * revert using the spread operator in tests * revert using the spread operator in invalid tests * refactor to handle Vue.component(...) and improve options * improved documentation with usage example * added tests recommended by @armano2 * ignore rule when file has multiple components * accept mixed cases between component name and file name * apply suggestions from @mysticatea * add quotes to file name in error message * improve docs * add shouldMatchCase option * Improve docs Co-Authored-By: rodrigopedra <rodrigo.pedra@gmail.com> * Update match-component-file-name.js * Update match-component-file-name.js
1 parent 78bd936 commit c6bbd95

File tree

5 files changed

+1075
-0
lines changed

5 files changed

+1075
-0
lines changed

‎README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi
237237

238238
| | Rule ID | Description |
239239
|:---|:--------|:------------|
240+
| | [vue/match-component-file-name](./docs/rules/match-component-file-name.md) | require component name property to match its file name |
240241
| :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in `<script>` |
241242

242243
### Deprecated

‎docs/rules/match-component-file-name.md

Lines changed: 204 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,204 @@
1+
# require component name property to match its file name (vue/match-component-file-name)
2+
3+
This rule reports if a component `name` property does not match its file name.
4+
5+
You can define an array of file extensions this rule should verify for
6+
the component's name.
7+
8+
## :book: Rule Details
9+
10+
This rule has some options.
11+
12+
```json
13+
{
14+
"vue/match-component-file-name": ["error", {
15+
"extensions": ["jsx"],
16+
"shouldMatchCase": false
17+
}]
18+
}
19+
```
20+
21+
By default this rule will only verify components in a file with a `.jsx`
22+
extension.
23+
24+
You can use any combination of `".jsx"`, `".vue"` and `".js"` extensions.
25+
26+
You can also enforce same case between the component's name and its file name.
27+
28+
If you are defining multiple components within the same file, this rule will be ignored.
29+
30+
:-1: Examples of **incorrect** code for this rule:
31+
32+
```jsx
33+
// file name: src/MyComponent.jsx
34+
export default {
35+
name: 'MComponent', // note the missing y
36+
render: () {
37+
return <h1>Hello world</h1>
38+
}
39+
}
40+
```
41+
42+
```vue
43+
// file name: src/MyComponent.vue
44+
// options: {extensions: ["vue"]}
45+
<script>
46+
export default {
47+
name: 'MComponent',
48+
template: '<div />'
49+
}
50+
</script>
51+
```
52+
53+
```js
54+
// file name: src/MyComponent.js
55+
// options: {extensions: ["js"]}
56+
new Vue({
57+
name: 'MComponent',
58+
template: '<div />'
59+
})
60+
```
61+
62+
```js
63+
// file name: src/MyComponent.js
64+
// options: {extensions: ["js"]}
65+
Vue.component('MComponent', {
66+
template: '<div />'
67+
})
68+
```
69+
70+
```jsx
71+
// file name: src/MyComponent.jsx
72+
// options: {shouldMatchCase: true}
73+
export default {
74+
name: 'my-component',
75+
render() { return <div /> }
76+
}
77+
```
78+
79+
```jsx
80+
// file name: src/my-component.jsx
81+
// options: {shouldMatchCase: true}
82+
export default {
83+
name: 'MyComponent',
84+
render() { return <div /> }
85+
}
86+
```
87+
88+
:+1: Examples of **correct** code for this rule:
89+
90+
```jsx
91+
// file name: src/MyComponent.jsx
92+
export default {
93+
name: 'MyComponent',
94+
render: () {
95+
return <h1>Hello world</h1>
96+
}
97+
}
98+
```
99+
100+
```jsx
101+
// file name: src/MyComponent.jsx
102+
// no name property defined
103+
export default {
104+
render: () {
105+
return <h1>Hello world</h1>
106+
}
107+
}
108+
```
109+
110+
```vue
111+
// file name: src/MyComponent.vue
112+
<script>
113+
export default {
114+
name: 'MyComponent',
115+
template: '<div />'
116+
}
117+
</script>
118+
```
119+
120+
```vue
121+
// file name: src/MyComponent.vue
122+
<script>
123+
export default {
124+
template: '<div />'
125+
}
126+
</script>
127+
```
128+
129+
```js
130+
// file name: src/MyComponent.js
131+
new Vue({
132+
name: 'MyComponent',
133+
template: '<div />'
134+
})
135+
```
136+
137+
```js
138+
// file name: src/MyComponent.js
139+
new Vue({
140+
template: '<div />'
141+
})
142+
```
143+
144+
```js
145+
// file name: src/MyComponent.js
146+
Vue.component('MyComponent', {
147+
template: '<div />'
148+
})
149+
```
150+
151+
```js
152+
// file name: src/components.js
153+
// defines multiple components, so this rule is ignored
154+
Vue.component('MyComponent', {
155+
template: '<div />'
156+
})
157+
158+
Vue.component('OtherComponent', {
159+
template: '<div />'
160+
})
161+
162+
new Vue({
163+
name: 'ThirdComponent',
164+
template: '<div />'
165+
})
166+
```
167+
168+
```jsx
169+
// file name: src/MyComponent.jsx
170+
// options: {shouldMatchCase: true}
171+
export default {
172+
name: 'MyComponent',
173+
render() { return <div /> }
174+
}
175+
```
176+
177+
```jsx
178+
// file name: src/my-component.jsx
179+
// options: {shouldMatchCase: true}
180+
export default {
181+
name: 'my-component',
182+
render() { return <div /> }
183+
}
184+
```
185+
186+
## :wrench: Options
187+
188+
```json
189+
{
190+
"vue/match-component-file-name": ["error", {
191+
"extensions": ["jsx"],
192+
"shouldMatchCase": false
193+
}]
194+
}
195+
```
196+
197+
- `"extensions": []` ... array of file extensions to be verified. Default is set to `["jsx"]`.
198+
- `"shouldMatchCase": false` ... boolean indicating if component's name
199+
should also match its file name case. Default is set to `false`.
200+
201+
## :books: Further reading
202+
203+
- [Style guide - Single-file component filename casing](https://vuejs.org/v2/style-guide/#Single-file-component-filename-casing-strongly-recommended)
204+

‎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'),

‎lib/rules/match-component-file-name.js

Lines changed: 140 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,140 @@
1+
/**
2+
* @fileoverview Require component name property to match its file name
3+
* @author Rodrigo Pedra Brum <rodrigo.pedra@gmail.com>
4+
*/
5+
'use strict'
6+
7+
// ------------------------------------------------------------------------------
8+
// Requirements
9+
// ------------------------------------------------------------------------------
10+
11+
const utils = require('../utils')
12+
const casing = require('../utils/casing')
13+
const path = require('path')
14+
15+
// ------------------------------------------------------------------------------
16+
// Rule Definition
17+
// ------------------------------------------------------------------------------
18+
19+
module.exports = {
20+
meta: {
21+
docs: {
22+
description: 'require component name property to match its file name',
23+
category: undefined,
24+
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.5/docs/rules/match-component-file-name.md'
25+
},
26+
fixable: null,
27+
schema: [
28+
{
29+
type: 'object',
30+
properties: {
31+
extensions: {
32+
type: 'array',
33+
items: {
34+
type: 'string'
35+
},
36+
uniqueItems: true,
37+
additionalItems: false
38+
},
39+
shouldMatchCase: {
40+
type: 'boolean'
41+
}
42+
},
43+
additionalProperties: false
44+
}
45+
]
46+
},
47+
48+
create (context) {
49+
const options = context.options[0]
50+
const shouldMatchCase = (options && options.shouldMatchCase) || false
51+
const extensionsArray = options && options.extensions
52+
const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx']
53+
54+
const extension = path.extname(context.getFilename())
55+
const filename = path.basename(context.getFilename(), extension)
56+
57+
const errors = []
58+
let componentCount = 0
59+
60+
if (!allowedExtensions.includes(extension.replace(/^\./, ''))) {
61+
return {}
62+
}
63+
64+
// ----------------------------------------------------------------------
65+
// Private
66+
// ----------------------------------------------------------------------
67+
68+
function compareNames (name, filename) {
69+
if (shouldMatchCase) {
70+
return name === filename
71+
}
72+
73+
return casing.pascalCase(name) === filename || casing.kebabCase(name) === filename
74+
}
75+
76+
function verifyName (node) {
77+
let name
78+
if (node.type === 'TemplateLiteral') {
79+
const quasis = node.quasis[0]
80+
name = quasis.value.cooked
81+
} else {
82+
name = node.value
83+
}
84+
85+
if (!compareNames(name, filename)) {
86+
errors.push({
87+
node: node,
88+
message: 'Component name `{{name}}` should match file name `{{filename}}`.',
89+
data: { filename, name }
90+
})
91+
}
92+
}
93+
94+
function canVerify (node) {
95+
return node.type === 'Literal' || (
96+
node.type === 'TemplateLiteral' &&
97+
node.expressions.length === 0 &&
98+
node.quasis.length === 1
99+
)
100+
}
101+
102+
return Object.assign({},
103+
{
104+
"CallExpression > MemberExpression > Identifier[name='component']" (node) {
105+
const parent = node.parent.parent
106+
const calleeObject = utils.unwrapTypes(parent.callee.object)
107+
108+
if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') {
109+
if (parent.arguments && parent.arguments.length === 2) {
110+
const argument = parent.arguments[0]
111+
if (canVerify(argument)) {
112+
verifyName(argument)
113+
}
114+
}
115+
}
116+
}
117+
},
118+
utils.executeOnVue(context, (object) => {
119+
const node = object.properties
120+
.find(item => (
121+
item.type === 'Property' &&
122+
item.key.name === 'name' &&
123+
canVerify(item.value)
124+
))
125+
126+
componentCount++
127+
128+
if (!node) return
129+
verifyName(node.value)
130+
}),
131+
{
132+
'Program:exit' () {
133+
if (componentCount > 1) return
134+
135+
errors.forEach((error) => context.report(error))
136+
}
137+
}
138+
)
139+
}
140+
}

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /