-
-
Notifications
You must be signed in to change notification settings - Fork 679
⭐️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
ota-meshi
merged 19 commits into
vuejs:master
from
rodrigopedra:match-component-file-name
Nov 24, 2018
Merged
Changes from all commits
Commits
Show all changes
19 commits
Select commit
Hold shift + click to select a range
7681c4b
match-component-file-name rule
rodrigopedra 667baa2
add tests when there is no name attribute and improve error message
rodrigopedra 194d8a6
apply suggestions from @armano2
rodrigopedra 099f678
refactor to have file extensions in options
rodrigopedra 578d53c
revert using the spread operator in tests
rodrigopedra 2fe1e35
revert using the spread operator in invalid tests
rodrigopedra f92c504
refactor to handle Vue.component(...) and improve options
rodrigopedra 3708156
improved documentation with usage example
rodrigopedra 2a60652
added tests recommended by @armano2
rodrigopedra 3d32aa6
ignore rule when file has multiple components
rodrigopedra 8ffac2e
accept mixed cases between component name and file name
rodrigopedra 8a5049a
apply suggestions from @mysticatea
rodrigopedra 984cbd7
add quotes to file name in error message
rodrigopedra 57eb9fb
improve docs
rodrigopedra 81b18fe
add shouldMatchCase option
rodrigopedra f5061ad
Improve docs
armano2 4a1438a
Merge branch 'master' into match-component-file-name
ota-meshi ef1d19e
Update match-component-file-name.js
ota-meshi 2453ecc
Update match-component-file-name.js
ota-meshi File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,204 @@ | ||
# require component name property to match its file name (vue/match-component-file-name) | ||
|
||
This rule reports if a component `name` property does not match its file name. | ||
|
||
You can define an array of file extensions this rule should verify for | ||
the component's name. | ||
|
||
## :book: Rule Details | ||
|
||
This rule has some options. | ||
|
||
```json | ||
{ | ||
"vue/match-component-file-name": ["error", { | ||
"extensions": ["jsx"], | ||
"shouldMatchCase": false | ||
}] | ||
} | ||
``` | ||
|
||
By default this rule will only verify components in a file with a `.jsx` | ||
extension. | ||
|
||
You can use any combination of `".jsx"`, `".vue"` and `".js"` extensions. | ||
|
||
You can also enforce same case between the component's name and its file name. | ||
|
||
If you are defining multiple components within the same file, this rule will be ignored. | ||
|
||
:-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> | ||
} | ||
} | ||
``` | ||
|
||
```vue | ||
// file name: src/MyComponent.vue | ||
// options: {extensions: ["vue"]} | ||
<script> | ||
export default { | ||
name: 'MComponent', | ||
template: '<div />' | ||
} | ||
</script> | ||
``` | ||
|
||
```js | ||
// file name: src/MyComponent.js | ||
// options: {extensions: ["js"]} | ||
new Vue({ | ||
name: 'MComponent', | ||
template: '<div />' | ||
}) | ||
``` | ||
|
||
```js | ||
// file name: src/MyComponent.js | ||
// options: {extensions: ["js"]} | ||
Vue.component('MComponent', { | ||
template: '<div />' | ||
}) | ||
``` | ||
|
||
```jsx | ||
// file name: src/MyComponent.jsx | ||
// options: {shouldMatchCase: true} | ||
export default { | ||
name: 'my-component', | ||
render() { return <div /> } | ||
} | ||
``` | ||
|
||
```jsx | ||
// file name: src/my-component.jsx | ||
// options: {shouldMatchCase: true} | ||
export default { | ||
name: 'MyComponent', | ||
render() { return <div /> } | ||
} | ||
``` | ||
|
||
:+1: Examples of **correct** code for this rule: | ||
|
||
```jsx | ||
// file name: src/MyComponent.jsx | ||
export default { | ||
name: 'MyComponent', | ||
render: () { | ||
return <h1>Hello world</h1> | ||
} | ||
} | ||
``` | ||
|
||
```jsx | ||
// file name: src/MyComponent.jsx | ||
// no name property defined | ||
export default { | ||
render: () { | ||
return <h1>Hello world</h1> | ||
} | ||
} | ||
``` | ||
|
||
```vue | ||
// file name: src/MyComponent.vue | ||
<script> | ||
export default { | ||
name: 'MyComponent', | ||
template: '<div />' | ||
} | ||
</script> | ||
``` | ||
|
||
```vue | ||
// file name: src/MyComponent.vue | ||
<script> | ||
export default { | ||
template: '<div />' | ||
} | ||
</script> | ||
``` | ||
|
||
```js | ||
// file name: src/MyComponent.js | ||
new Vue({ | ||
name: 'MyComponent', | ||
template: '<div />' | ||
}) | ||
``` | ||
|
||
```js | ||
// file name: src/MyComponent.js | ||
new Vue({ | ||
template: '<div />' | ||
}) | ||
``` | ||
|
||
```js | ||
// file name: src/MyComponent.js | ||
Vue.component('MyComponent', { | ||
template: '<div />' | ||
}) | ||
``` | ||
|
||
```js | ||
// file name: src/components.js | ||
// defines multiple components, so this rule is ignored | ||
Vue.component('MyComponent', { | ||
template: '<div />' | ||
}) | ||
|
||
Vue.component('OtherComponent', { | ||
template: '<div />' | ||
}) | ||
|
||
new Vue({ | ||
name: 'ThirdComponent', | ||
template: '<div />' | ||
}) | ||
``` | ||
|
||
```jsx | ||
// file name: src/MyComponent.jsx | ||
// options: {shouldMatchCase: true} | ||
export default { | ||
name: 'MyComponent', | ||
render() { return <div /> } | ||
} | ||
``` | ||
|
||
```jsx | ||
// file name: src/my-component.jsx | ||
// options: {shouldMatchCase: true} | ||
export default { | ||
name: 'my-component', | ||
render() { return <div /> } | ||
} | ||
``` | ||
|
||
## :wrench: Options | ||
|
||
```json | ||
{ | ||
"vue/match-component-file-name": ["error", { | ||
"extensions": ["jsx"], | ||
"shouldMatchCase": false | ||
}] | ||
} | ||
``` | ||
|
||
- `"extensions": []` ... array of file extensions to be verified. Default is set to `["jsx"]`. | ||
- `"shouldMatchCase": false` ... boolean indicating if component's name | ||
should also match its file name case. Default is set to `false`. | ||
rodrigopedra marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
## :books: Further reading | ||
|
||
- [Style guide - Single-file component filename casing](https://vuejs.org/v2/style-guide/#Single-file-component-filename-casing-strongly-recommended) | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,140 @@ | ||
/** | ||
* @fileoverview Require component name property to match its file name | ||
* @author Rodrigo Pedra Brum <rodrigo.pedra@gmail.com> | ||
*/ | ||
'use strict' | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Requirements | ||
// ------------------------------------------------------------------------------ | ||
|
||
const utils = require('../utils') | ||
const casing = require('../utils/casing') | ||
const path = require('path') | ||
|
||
// ------------------------------------------------------------------------------ | ||
// Rule Definition | ||
// ------------------------------------------------------------------------------ | ||
|
||
module.exports = { | ||
meta: { | ||
docs: { | ||
description: 'require component name property to match its file name', | ||
category: undefined, | ||
url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.5/docs/rules/match-component-file-name.md' | ||
}, | ||
fixable: null, | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
extensions: { | ||
type: 'array', | ||
items: { | ||
type: 'string' | ||
}, | ||
uniqueItems: true, | ||
additionalItems: false | ||
}, | ||
shouldMatchCase: { | ||
type: 'boolean' | ||
} | ||
}, | ||
additionalProperties: false | ||
} | ||
] | ||
}, | ||
|
||
create (context) { | ||
const options = context.options[0] | ||
const shouldMatchCase = (options && options.shouldMatchCase) || false | ||
const extensionsArray = options && options.extensions | ||
const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx'] | ||
|
||
const extension = path.extname(context.getFilename()) | ||
const filename = path.basename(context.getFilename(), extension) | ||
|
||
const errors = [] | ||
let componentCount = 0 | ||
|
||
if (!allowedExtensions.includes(extension.replace(/^\./, ''))) { | ||
return {} | ||
} | ||
|
||
// ---------------------------------------------------------------------- | ||
// Private | ||
// ---------------------------------------------------------------------- | ||
|
||
function compareNames (name, filename) { | ||
if (shouldMatchCase) { | ||
return name === filename | ||
} | ||
|
||
return casing.pascalCase(name) === filename || casing.kebabCase(name) === filename | ||
} | ||
|
||
function verifyName (node) { | ||
let name | ||
if (node.type === 'TemplateLiteral') { | ||
const quasis = node.quasis[0] | ||
name = quasis.value.cooked | ||
mysticatea marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} else { | ||
name = node.value | ||
} | ||
|
||
if (!compareNames(name, filename)) { | ||
errors.push({ | ||
node: node, | ||
message: 'Component name `{{name}}` should match file name `{{filename}}`.', | ||
data: { filename, name } | ||
}) | ||
} | ||
} | ||
|
||
function canVerify (node) { | ||
return node.type === 'Literal' || ( | ||
node.type === 'TemplateLiteral' && | ||
node.expressions.length === 0 && | ||
node.quasis.length === 1 | ||
) | ||
} | ||
|
||
return Object.assign({}, | ||
{ | ||
"CallExpression > MemberExpression > Identifier[name='component']" (node) { | ||
const parent = node.parent.parent | ||
const calleeObject = utils.unwrapTypes(parent.callee.object) | ||
|
||
if (calleeObject.type === 'Identifier' && calleeObject.name === 'Vue') { | ||
if (parent.arguments && parent.arguments.length === 2) { | ||
const argument = parent.arguments[0] | ||
if (canVerify(argument)) { | ||
verifyName(argument) | ||
} | ||
} | ||
} | ||
} | ||
}, | ||
utils.executeOnVue(context, (object) => { | ||
const node = object.properties | ||
.find(item => ( | ||
item.type === 'Property' && | ||
item.key.name === 'name' && | ||
canVerify(item.value) | ||
)) | ||
|
||
componentCount++ | ||
|
||
if (!node) return | ||
verifyName(node.value) | ||
}), | ||
{ | ||
'Program:exit' () { | ||
if (componentCount > 1) return | ||
|
||
errors.forEach((error) => context.report(error)) | ||
} | ||
} | ||
) | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.