From 7681c4bc2fc2f2aec75964f770abe490b7cc6039 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Fri, 16 Nov 2018 23:25:57 -0200 Subject: [PATCH 01/18] match-component-file-name rule --- README.md | 1 + docs/rules/match-component-file-name.md | 50 ++++++ lib/configs/recommended.js | 1 + lib/index.js | 1 + lib/rules/match-component-file-name.js | 57 +++++++ tests/lib/rules/match-component-file-name.js | 155 +++++++++++++++++++ 6 files changed, 265 insertions(+) create mode 100644 docs/rules/match-component-file-name.md create mode 100644 lib/rules/match-component-file-name.js create mode 100644 tests/lib/rules/match-component-file-name.js diff --git a/README.md b/README.md index 0ec452586..f522f2406 100644 --- a/README.md +++ b/README.md @@ -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 | diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md new file mode 100644 index 000000000..ad469f496 --- /dev/null +++ b/docs/rules/match-component-file-name.md @@ -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

Hello world

+ } +} +``` + +:+1: Examples of **correct** code for this rule: + +```jsx +// file name: src/MyComponent.jsx +export default { + name: 'MyComponent', + render: () { + return

Hello world

+ } +} +``` + +## :wrench: Options + +- `"jsx"` (default) ... verify components in files with `.jsx` extension. +- `"both"` ... verify components in files with both `.jsx` and `.vue` extensions. diff --git a/lib/configs/recommended.js b/lib/configs/recommended.js index 8c9dbad4a..26d62535a 100644 --- a/lib/configs/recommended.js +++ b/lib/configs/recommended.js @@ -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' diff --git a/lib/index.js b/lib/index.js index 5952af45a..e6e5a8904 100644 --- a/lib/index.js +++ b/lib/index.js @@ -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'), diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js new file mode 100644 index 000000000..93733bd08 --- /dev/null +++ b/lib/rules/match-component-file-name.js @@ -0,0 +1,57 @@ +/** + * @fileoverview Require component name property to match its file name + * @author Rodrigo Pedra Brum + */ +'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 } + }) + } + }) + } +} diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js new file mode 100644 index 000000000..fbf1012d5 --- /dev/null +++ b/tests/lib/rules/match-component-file-name.js @@ -0,0 +1,155 @@ +/** + * @fileoverview Require component name property to match its file name + * @author Rodrigo Pedra Brum + */ +'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
} + } + ` + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'MyComponent', + render() { return
} + } + `, + options: ['jsx'] + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'MyComponent', + render() { return
} + } + `, + options: ['both'] + } + ], + + invalid: [ + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'MComponent', + render() { return
} + } + `, + errors: [{ + message: 'Component name should match file name (MyComponent / MComponent).' + }] + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'MComponent', + render() { return
} + } + `, + options: ['jsx'], + errors: [{ + message: 'Component name should match file name (MyComponent / MComponent).' + }] + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'MComponent', + render() { return
} + } + `, + options: ['both'], + errors: [{ + message: 'Component name should match file name (MyComponent / MComponent).' + }] + } + ] +}) + +vueRuleTester.run('match-component-file-name', rule, { + valid: [ + { + filename: 'MyComponent.vue', + code: ` + + ` // missing ["both"] option, so the file is ignored + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: ['both'] + } + ], + + invalid: [ + { + filename: 'MyComponent.vue', + code: ` + + `, + options: ['both'], + errors: [{ + message: 'Component name should match file name (MyComponent / MComponent).' + }] + } + ] +}) From 667baa22f2997fe9d2f36c6770f68bd7531262d1 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Fri, 16 Nov 2018 23:35:29 -0200 Subject: [PATCH 02/18] add tests when there is no name attribute and improve error message --- lib/rules/match-component-file-name.js | 4 +- tests/lib/rules/match-component-file-name.js | 45 ++++++++++++++++++-- 2 files changed, 43 insertions(+), 6 deletions(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 93733bd08..2b0cc20d3 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -19,7 +19,7 @@ module.exports = { meta: { docs: { description: 'require component name property to match its file name', - category: 'recommended', + category: undefined, url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.4/docs/rules/match-component-file-name.md' }, fixable: null, @@ -48,7 +48,7 @@ module.exports = { context.report({ obj: object, loc: object.loc, - message: 'Component name should match file name ({{filename}} / {{name}}).', + message: 'Component name `{{name}}` should match file name {{filename}}.', data: { filename, name } }) } diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index fbf1012d5..5e8d8e52f 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -44,6 +44,14 @@ jsxRuleTester.run('match-component-file-name', rule, { } ` }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + render() { return
} + } + ` + }, { filename: 'MyComponent.jsx', code: ` @@ -54,6 +62,15 @@ jsxRuleTester.run('match-component-file-name', rule, { `, options: ['jsx'] }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + render() { return
} + } + `, + options: ['jsx'] + }, { filename: 'MyComponent.jsx', code: ` @@ -63,6 +80,15 @@ jsxRuleTester.run('match-component-file-name', rule, { } `, options: ['both'] + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + render() { return
} + } + `, + options: ['both'] } ], @@ -76,7 +102,7 @@ jsxRuleTester.run('match-component-file-name', rule, { } `, errors: [{ - message: 'Component name should match file name (MyComponent / MComponent).' + message: 'Component name `MComponent` should match file name MyComponent.' }] }, { @@ -89,7 +115,7 @@ jsxRuleTester.run('match-component-file-name', rule, { `, options: ['jsx'], errors: [{ - message: 'Component name should match file name (MyComponent / MComponent).' + message: 'Component name `MComponent` should match file name MyComponent.' }] }, { @@ -102,7 +128,7 @@ jsxRuleTester.run('match-component-file-name', rule, { `, options: ['both'], errors: [{ - message: 'Component name should match file name (MyComponent / MComponent).' + message: 'Component name `MComponent` should match file name MyComponent.' }] } ] @@ -132,6 +158,17 @@ vueRuleTester.run('match-component-file-name', rule, { `, options: ['both'] + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: ['both'] } ], @@ -148,7 +185,7 @@ vueRuleTester.run('match-component-file-name', rule, { `, options: ['both'], errors: [{ - message: 'Component name should match file name (MyComponent / MComponent).' + message: 'Component name `MComponent` should match file name MyComponent.' }] } ] From 194d8a61865c5c00717082b7fc3f3d736d2f230a Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Fri, 16 Nov 2018 23:41:15 -0200 Subject: [PATCH 03/18] apply suggestions from @armano2 --- lib/rules/match-component-file-name.js | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 2b0cc20d3..2ac711535 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -30,14 +30,28 @@ module.exports = { create (context) { return utils.executeOnVueComponent(context, (object) => { - const nameProperty = object.properties.find((prop) => prop.key.name === 'name') + const nameProperty = object.properties + .find(p => + p.type === 'Property' && + p.key.type === 'Identifier' && + p.key.name === 'name' && + ( + p.value.type === 'Literal' || ( + p.value.type === 'TemplateLiteral' && + p.value.expressions.length === 0 && + p.value.quasis.length === 1 + ) + ) + ) if (!nameProperty) { return } const allowedExtensions = context.options[0] || 'jsx' - const name = nameProperty.value.value + const name = nameProperty.value.type === 'TemplateLiteral' + ? nameProperty.quasis[0].value.cooked + : nameProperty.value.value const [, filename, extension] = /^(.+?)\.(.*)$/g.exec(path.basename(context.getFilename())) if (extension === 'vue' && allowedExtensions !== 'both') { From 099f6786059a37c80854eecd75024600af41e4ab Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 00:41:21 -0200 Subject: [PATCH 04/18] refactor to have file extensions in options --- README.md | 2 +- docs/rules/match-component-file-name.md | 21 +- lib/configs/recommended.js | 1 - lib/rules/match-component-file-name.js | 35 +-- tests/lib/rules/match-component-file-name.js | 223 ++++++++++++------- 5 files changed, 168 insertions(+), 114 deletions(-) diff --git a/README.md b/README.md index f522f2406..46a832d13 100644 --- a/README.md +++ b/README.md @@ -224,7 +224,6 @@ 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 | @@ -234,6 +233,7 @@ Enforce all the rules in this category, as well as all higher priority rules, wi | | Rule ID | Description | |:---|:--------|:------------| | :wrench: | [vue/component-name-in-template-casing](./docs/rules/component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | +| | [vue/match-component-file-name](./docs/rules/match-component-file-name.md) | require component name property to match its file name | | :wrench: | [vue/multiline-html-element-content-newline](./docs/rules/multiline-html-element-content-newline.md) | require a line break before and after the contents of a multiline element | | :wrench: | [vue/no-spaces-around-equal-signs-in-attribute](./docs/rules/no-spaces-around-equal-signs-in-attribute.md) | disallow spaces around equal signs in attribute | | :wrench: | [vue/script-indent](./docs/rules/script-indent.md) | enforce consistent indentation in ` `, - options: ['both'] + parser: 'vue-eslint-parser', + parserOptions // options default to [['jsx']] }, { - filename: 'MyComponent.jsx', + filename: 'MyComponent.vue', code: ` - export default { - render() { return
} - } + `, - options: ['both'] - } - ], + options: [['jsx']], // missing 'vue' in options + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [['vue']], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [['vue']], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [['vue']], + parser: 'vue-eslint-parser', + parserOptions + }, - invalid: [ + // .js { - filename: 'MyComponent.jsx', + filename: 'MyComponent.js', code: ` - export default { + new Vue({ name: 'MComponent', - render() { return
} - } + template: '
' + }) `, - errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' - }] + parserOptions // options default to [['jsx']] + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: 'MComponent', + template: '
' + }) + `, + options: [['vue']], // missing 'js' in options + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + template: '
' + }) + `, + options: [['js']], + parserOptions }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: 'MyComponent', + template: '
' + }) + `, + options: [['js']], + parserOptions + } + ], + + invalid: [ + // .jsx { filename: 'MyComponent.jsx', code: ` @@ -113,7 +189,7 @@ jsxRuleTester.run('match-component-file-name', rule, { render() { return
} } `, - options: ['jsx'], + parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }}, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' }] @@ -126,16 +202,14 @@ jsxRuleTester.run('match-component-file-name', rule, { render() { return
} } `, - options: ['both'], + options: [['jsx']], + parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }}, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' }] - } - ] -}) + }, -vueRuleTester.run('match-component-file-name', rule, { - valid: [ + // .vue { filename: 'MyComponent.vue', code: ` @@ -145,45 +219,26 @@ vueRuleTester.run('match-component-file-name', rule, { template: '
' } - ` // missing ["both"] option, so the file is ignored - }, - { - filename: 'MyComponent.vue', - code: ` - `, - options: ['both'] + options: [['vue']], + parser: 'vue-eslint-parser', + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] }, - { - filename: 'MyComponent.vue', - code: ` - - `, - options: ['both'] - } - ], - invalid: [ + // .js { - filename: 'MyComponent.vue', + filename: 'MyComponent.js', code: ` - + new Vue({ + name: 'MComponent', + template: '
' + }) `, - options: ['both'], + options: [['js']], + parserOptions, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' }] From 578d53cf2b1a2e0170acbb77b80d1eedbfda8dc9 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 00:44:24 -0200 Subject: [PATCH 05/18] revert using the spread operator in tests --- tests/lib/rules/match-component-file-name.js | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index 19f9e3ad4..1f0abe428 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -11,6 +11,12 @@ const rule = require('../../../lib/rules/match-component-file-name') const RuleTester = require('eslint').RuleTester +const jsxParserOptions = { + ecmaVersion: 2018, + sourceType: 'module', + ecmaFeatures: { jsx: true } +} + const parserOptions = { ecmaVersion: 2018, sourceType: 'module' @@ -32,7 +38,7 @@ ruleTester.run('match-component-file-name', rule, { render() { return
} } `, - parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }} + parserOptions: jsxParserOptions }, { filename: 'MyComponent.jsx', @@ -42,7 +48,7 @@ ruleTester.run('match-component-file-name', rule, { render() { return
} } `, - parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }} + parserOptions: jsxParserOptions }, { filename: 'MyComponent.jsx', @@ -53,7 +59,7 @@ ruleTester.run('match-component-file-name', rule, { } `, options: [['jsx']], - parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }} + parserOptions: jsxParserOptions }, { filename: 'MyComponent.jsx', @@ -64,7 +70,7 @@ ruleTester.run('match-component-file-name', rule, { } `, options: [['vue']], // missing jsx in options - parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }} + parserOptions: jsxParserOptions }, // .vue From 2fe1e359487978c0b3602e862249b20ac30f2497 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 00:45:41 -0200 Subject: [PATCH 06/18] revert using the spread operator in invalid tests --- tests/lib/rules/match-component-file-name.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index 1f0abe428..51b7330b1 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -195,7 +195,7 @@ ruleTester.run('match-component-file-name', rule, { render() { return
} } `, - parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }}, + parserOptions: jsxParserOptions, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' }] @@ -209,7 +209,7 @@ ruleTester.run('match-component-file-name', rule, { } `, options: [['jsx']], - parserOptions: { ...parserOptions, ecmaFeatures: { jsx: true }}, + parserOptions: jsxParserOptions, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' }] From f92c5040ce53340c31db2a87587ce6d80d6687e4 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 01:23:33 -0200 Subject: [PATCH 07/18] refactor to handle Vue.component(...) and improve options --- docs/rules/match-component-file-name.md | 80 +++++++++++++++- lib/rules/match-component-file-name.js | 99 +++++++++++++------- tests/lib/rules/match-component-file-name.js | 66 ++++++++++--- 3 files changed, 198 insertions(+), 47 deletions(-) diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md index 9371f9685..c03622e83 100644 --- a/docs/rules/match-component-file-name.md +++ b/docs/rules/match-component-file-name.md @@ -22,6 +22,31 @@ export default { } ``` +```vue +// file name: src/MyComponent.vue + +``` + +```js +// file name: src/MyComponent.js +new Vue({ + name: 'MComponent', + template: '
' +}) +``` + +```js +// file name: src/MyComponent.js +Vue.component('MComponent', { + template: '
' +}) +``` + :+1: Examples of **correct** code for this rule: ```jsx @@ -34,8 +59,59 @@ export default { } ``` +```jsx +// file name: src/MyComponent.jsx +// no name property defined +export default { + render: () { + return

Hello world

+ } +} +``` + +```vue +// file name: src/MyComponent.vue + +``` + +```vue +// file name: src/MyComponent.vue + +``` + +```js +// file name: src/MyComponent.js +new Vue({ + name: 'MyComponent', + template: '
' +}) +``` + +```js +// file name: src/MyComponent.js +new Vue({ + template: '
' +}) +``` + +```js +// file name: src/MyComponent.js +Vue.component('MyComponent', { + template: '
' +}) +``` + ## :wrench: Options -- `['jsx']` (default) ... verify components in files with `.jsx` extension. -- `['jsx', 'vue', 'js']` (*or any combinations of these extensions*) +- `{extensions: ['jsx']}` (default) ... verify components in files with `.jsx` extension. +- `{extensions: ['jsx', 'vue', 'js']}` (*or any combinations of these extensions*) ... verify components in files with listed extensions. diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 717fe4edd..a26d6b250 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -25,56 +25,89 @@ module.exports = { fixable: null, schema: [ { - type: 'array', - items: { - type: 'string' + type: 'object', + properties: { + extensions: { + type: 'array', + items: { + type: 'string' + }, + uniqueItems: true, + additionalItems: false + } }, - uniqueItems: true, - additionalItems: false + additionalProperties: false } ] }, create (context) { const options = context.options[0] - const allowedExtensions = Array.isArray(options) ? options : ['jsx'] + const extensionsArray = options && options.extensions + const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx'] + const [, filename, extension] = /^(.+?)\.(.+)$/g.exec(path.basename(context.getFilename())) - return utils.executeOnVue(context, (object) => { - const nameProperty = object.properties - .find(item => - item.type === 'Property' && - item.key.name === 'name' && - item.key.type === 'Identifier' && - ( - item.value.type === 'Literal' || ( - item.value.type === 'TemplateLiteral' && - item.value.expressions.length === 0 && - item.value.quasis.length === 1 - ) - ) - ) + if (!allowedExtensions.includes(extension)) { + return {} + } - if (!nameProperty) { - return - } - - const name = nameProperty.value.type === 'TemplateLiteral' - ? nameProperty.quasis[0].value.cooked - : nameProperty.value.value - const [, filename, extension] = /^(.+?)\.(.+)$/g.exec(path.basename(context.getFilename())) + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- - if (!allowedExtensions.includes(extension)) { - return + function verifyName (node) { + let name + if (node.type === 'TemplateLiteral') { + const quasis = node.quasis[0] + name = quasis.value.cooked + } else { + name = node.value } if (name !== filename) { context.report({ - obj: object, - loc: object.loc, + 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, (obj) => { + const node = obj.properties + .find(item => ( + item.type === 'Property' && + item.key.name === 'name' && + canVerify(item.value) + )) + + if (!node) return + verifyName(node.value) + }) + ) } } diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index 51b7330b1..cc2931943 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -58,7 +58,7 @@ ruleTester.run('match-component-file-name', rule, { render() { return
} } `, - options: [['jsx']], + options: [{ extensions: ['jsx'] }], parserOptions: jsxParserOptions }, { @@ -69,7 +69,7 @@ ruleTester.run('match-component-file-name', rule, { render() { return
} } `, - options: [['vue']], // missing jsx in options + options: [{ extensions: ['vue'] }], // missing jsx in options parserOptions: jsxParserOptions }, @@ -97,7 +97,7 @@ ruleTester.run('match-component-file-name', rule, { } `, - options: [['jsx']], // missing 'vue' in options + options: [{ extensions: ['jsx'] }], // missing jsx in options parser: 'vue-eslint-parser', parserOptions }, @@ -110,7 +110,7 @@ ruleTester.run('match-component-file-name', rule, { } `, - options: [['vue']], + options: [{ extensions: ['vue'] }], parser: 'vue-eslint-parser', parserOptions }, @@ -121,7 +121,7 @@ ruleTester.run('match-component-file-name', rule, {
`, - options: [['vue']], + options: [{ extensions: ['vue'] }], parser: 'vue-eslint-parser', parserOptions }, @@ -135,7 +135,7 @@ ruleTester.run('match-component-file-name', rule, { } `, - options: [['vue']], + options: [{ extensions: ['vue'] }], parser: 'vue-eslint-parser', parserOptions }, @@ -151,6 +151,15 @@ ruleTester.run('match-component-file-name', rule, { `, parserOptions // options default to [['jsx']] }, + { + filename: 'MyComponent.js', + code: ` + Vue.component('MComponent', { + template: '
' + }) + `, + parserOptions // options default to [['jsx']] + }, { filename: 'MyComponent.js', code: ` @@ -159,7 +168,17 @@ ruleTester.run('match-component-file-name', rule, { template: '
' }) `, - options: [['vue']], // missing 'js' in options + options: [{ extensions: ['vue'] }], // missing 'js' in options + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component('MComponent', { + template: '
' + }) + `, + options: [{ extensions: ['vue'] }], // missing 'js' in options parserOptions }, { @@ -169,7 +188,7 @@ ruleTester.run('match-component-file-name', rule, { template: '
' }) `, - options: [['js']], + options: [{ extensions: ['js'] }], parserOptions }, { @@ -180,7 +199,17 @@ ruleTester.run('match-component-file-name', rule, { template: '
' }) `, - options: [['js']], + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component('MyComponent', { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], parserOptions } ], @@ -208,7 +237,7 @@ ruleTester.run('match-component-file-name', rule, { render() { return
} } `, - options: [['jsx']], + options: [{ extensions: ['jsx'] }], parserOptions: jsxParserOptions, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' @@ -226,7 +255,7 @@ ruleTester.run('match-component-file-name', rule, { } `, - options: [['vue']], + options: [{ extensions: ['vue'] }], parser: 'vue-eslint-parser', parserOptions, errors: [{ @@ -243,7 +272,20 @@ ruleTester.run('match-component-file-name', rule, { template: '
' }) `, - options: [['js']], + options: [{ extensions: ['js'] }], + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component('MComponent', { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], parserOptions, errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' From 3708156173372a3b2e74e5fc989e3b878da6c4ca Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 01:30:57 -0200 Subject: [PATCH 08/18] improved documentation with usage example --- docs/rules/match-component-file-name.md | 29 ++++++++++++++++++++----- 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md index c03622e83..64efd9caf 100644 --- a/docs/rules/match-component-file-name.md +++ b/docs/rules/match-component-file-name.md @@ -1,14 +1,26 @@ # 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"] + }] +} +``` + By default this rule will only verify components in a file with a `.jsx` extension. -## :book: Rule Details - -This rule reports if a component `name` property does not match its file name. +You can use any combination of `".jsx"`, `".vue"` and `".js"` extensions with this option. :-1: Examples of **incorrect** code for this rule: @@ -112,6 +124,11 @@ Vue.component('MyComponent', { ## :wrench: Options -- `{extensions: ['jsx']}` (default) ... verify components in files with `.jsx` extension. -- `{extensions: ['jsx', 'vue', 'js']}` (*or any combinations of these extensions*) - ... verify components in files with listed extensions. +- `{extensions: ["jsx"]}` (default) ... verify components in files with `.jsx` extension. +- `{extensions: ["vue"]}` (default) ... verify components in files with `.vue` extension. +- `{extensions: ["js"]}` (default) ... verify components in files with `.js` extension. +- `{extensions: ["jsx", "vue"]}` ... verify components in files with `.jsx` or `.vue` extensions. +- `{extensions: ["jsx", "js"]}` ... verify components in files with `.jsx` or `.js` extensions. +- `{extensions: ["vue", "js"]}` ... verify components in files with `.vue` or `.js` extensions. +- `{extensions: ["jsx", "vue", "js"]}` ... verify components in files with any of the + provided extensions. From 2a60652a5daeb311196a39ccbdde5ecb84bde51e Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 02:03:52 -0200 Subject: [PATCH 09/18] added tests recommended by @armano2 --- tests/lib/rules/match-component-file-name.js | 333 ++++++++++++++++++- 1 file changed, 332 insertions(+), 1 deletion(-) diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index cc2931943..f185394e2 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -30,7 +30,7 @@ const ruleTester = new RuleTester() ruleTester.run('match-component-file-name', rule, { valid: [ - // ,jsx + // .jsx { filename: 'MyComponent.jsx', code: ` @@ -61,6 +61,50 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['jsx'] }], parserOptions: jsxParserOptions }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: myComponent, + render() { return
} + } + `, + options: [{ extensions: ['jsx'] }], + parserOptions: jsxParserOptions + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name, + render() { return
} + } + `, + options: [{ extensions: ['jsx'] }], + parserOptions: jsxParserOptions + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: \`MyComponent\`, + render() { return
} + } + `, + options: [{ extensions: ['jsx'] }], + parserOptions: jsxParserOptions + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: \`My\${foo}\`, + render() { return
} + } + `, + options: [{ extensions: ['jsx'] }], + parserOptions: jsxParserOptions + }, { filename: 'MyComponent.jsx', code: ` @@ -139,6 +183,62 @@ ruleTester.run('match-component-file-name', rule, { parser: 'vue-eslint-parser', parserOptions }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions + }, // .js { @@ -151,6 +251,13 @@ ruleTester.run('match-component-file-name', rule, { `, parserOptions // options default to [['jsx']] }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({}) + `, + parserOptions // options default to [['jsx']] + }, { filename: 'MyComponent.js', code: ` @@ -171,6 +278,14 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['vue'] }], // missing 'js' in options parserOptions }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({}) + `, + options: [{ extensions: ['vue'] }], // missing 'js' in options + parserOptions + }, { filename: 'MyComponent.js', code: ` @@ -202,6 +317,108 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: myComponent, + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name, + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: \`MyComponent\`, + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: \`My\${foo}\`, + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({}) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue.mixin({ + name: 'MyComponent', + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue.mixin({ + name: myComponent, + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue.mixin({ + name + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue.mixin({ + name: \`MyComponent\`, + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + new Vue.mixin({ + name: \`My\${foo}\`, + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, { filename: 'MyComponent.js', code: ` @@ -211,6 +428,36 @@ ruleTester.run('match-component-file-name', rule, { `, options: [{ extensions: ['js'] }], parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component(myComponent, { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component(\`MyComponent\`, { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component(\`My\${foo}\`, { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions } ], @@ -243,6 +490,20 @@ ruleTester.run('match-component-file-name', rule, { message: 'Component name `MComponent` should match file name MyComponent.' }] }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: \`MComponent\`, + render() { return
} + } + `, + options: [{ extensions: ['jsx'] }], + parserOptions: jsxParserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] + }, // .vue { @@ -262,6 +523,23 @@ ruleTester.run('match-component-file-name', rule, { message: 'Component name `MComponent` should match file name MyComponent.' }] }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] + }, // .js { @@ -278,6 +556,46 @@ ruleTester.run('match-component-file-name', rule, { message: 'Component name `MComponent` should match file name MyComponent.' }] }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: \`MComponent\`, + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({ + name: 'MComponent', + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({ + name: \`MComponent\`, + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] + }, { filename: 'MyComponent.js', code: ` @@ -290,6 +608,19 @@ ruleTester.run('match-component-file-name', rule, { errors: [{ message: 'Component name `MComponent` should match file name MyComponent.' }] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component(\`MComponent\`, { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [{ + message: 'Component name `MComponent` should match file name MyComponent.' + }] } ] }) From 3d32aa6ae36a32e35c214ad8149e4e74d758290f Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 03:19:16 -0200 Subject: [PATCH 10/18] ignore rule when file has multiple components --- docs/rules/match-component-file-name.md | 19 +++++++++++++++++++ lib/rules/match-component-file-name.js | 20 ++++++++++++++++---- tests/lib/rules/match-component-file-name.js | 19 +++++++++++++++++++ 3 files changed, 54 insertions(+), 4 deletions(-) diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md index 64efd9caf..3782b082c 100644 --- a/docs/rules/match-component-file-name.md +++ b/docs/rules/match-component-file-name.md @@ -22,6 +22,8 @@ extension. You can use any combination of `".jsx"`, `".vue"` and `".js"` extensions with this option. +If you are defining multiple components within the same file, this rule will be ignored. + :-1: Examples of **incorrect** code for this rule: ```jsx @@ -122,6 +124,23 @@ Vue.component('MyComponent', { }) ``` +```js +// file name: src/components.js +// defines multiple components, so this rule is ignored +Vue.component('MyComponent', { + template: '
' +}) + +Vue.component('OtherComponent', { + template: '
' +}) + +new Vue({ + name: 'ThirdComponent', + template: '
' +}) +``` + ## :wrench: Options - `{extensions: ["jsx"]}` (default) ... verify components in files with `.jsx` extension. diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index a26d6b250..0a9f179e7 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -47,6 +47,9 @@ module.exports = { const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx'] const [, filename, extension] = /^(.+?)\.(.+)$/g.exec(path.basename(context.getFilename())) + const errors = [] + let componentCount = 0 + if (!allowedExtensions.includes(extension)) { return {} } @@ -65,7 +68,7 @@ module.exports = { } if (name !== filename) { - context.report({ + errors.push({ node: node, message: 'Component name `{{name}}` should match file name {{filename}}.', data: { filename, name } @@ -97,17 +100,26 @@ module.exports = { } } }, - utils.executeOnVue(context, (obj) => { - const node = obj.properties + 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)) + } + } ) } } diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index f185394e2..17ea501f5 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -458,6 +458,25 @@ ruleTester.run('match-component-file-name', rule, { `, options: [{ extensions: ['js'] }], parserOptions + }, + { + filename: 'index.js', + code: ` + Vue.component('MyComponent', { + template: '
' + }) + + Vue.component('OtherComponent', { + template: '
' + }) + + new Vue('OtherComponent', { + name: 'ThirdComponent', + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions } ], From 8ffac2efdc11efb0b91cd209f182a325329f866c Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 05:42:13 -0200 Subject: [PATCH 11/18] accept mixed cases between component name and file name --- lib/rules/match-component-file-name.js | 3 +- tests/lib/rules/match-component-file-name.js | 44 ++++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 0a9f179e7..3c076f50d 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -9,6 +9,7 @@ // ------------------------------------------------------------------------------ const utils = require('../utils') +const casing = require('../utils/casing') const path = require('path') // ------------------------------------------------------------------------------ @@ -67,7 +68,7 @@ module.exports = { name = node.value } - if (name !== filename) { + if (casing.pascalCase(name) !== filename && casing.kebabCase(name) !== filename) { errors.push({ node: node, message: 'Component name `{{name}}` should match file name {{filename}}.', diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index 17ea501f5..e0c52bccc 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -477,6 +477,50 @@ ruleTester.run('match-component-file-name', rule, { `, options: [{ extensions: ['js'] }], parserOptions + }, + + // mixed cases + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'my-component.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions + }, + { + filename: 'my-component.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: 'vue-eslint-parser', + parserOptions } ], From 8a5049a5fa905987b761d5c22bd3ac9b371f9c77 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 05:56:37 -0200 Subject: [PATCH 12/18] apply suggestions from @mysticatea --- lib/rules/match-component-file-name.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 3c076f50d..0b4e13508 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -46,12 +46,13 @@ module.exports = { const options = context.options[0] const extensionsArray = options && options.extensions const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx'] - const [, filename, extension] = /^(.+?)\.(.+)$/g.exec(path.basename(context.getFilename())) + const extension = path.extname(context.getFilename()) + const filename = path.basename(context.getFilename(), extension) const errors = [] let componentCount = 0 - if (!allowedExtensions.includes(extension)) { + if (!allowedExtensions.includes(extension.replace(/^\./, ''))) { return {} } From 984cbd7e9ab3dfc3b4c4cf80129946d396bf5a80 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 05:57:53 -0200 Subject: [PATCH 13/18] add quotes to file name in error message --- lib/rules/match-component-file-name.js | 2 +- tests/lib/rules/match-component-file-name.js | 22 ++++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 0b4e13508..3bdc5d376 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -72,7 +72,7 @@ module.exports = { if (casing.pascalCase(name) !== filename && casing.kebabCase(name) !== filename) { errors.push({ node: node, - message: 'Component name `{{name}}` should match file name {{filename}}.', + message: 'Component name `{{name}}` should match file name `{{filename}}`.', data: { filename, name } }) } diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index e0c52bccc..b4fdbe61a 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -536,7 +536,7 @@ ruleTester.run('match-component-file-name', rule, { `, parserOptions: jsxParserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -550,7 +550,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['jsx'] }], parserOptions: jsxParserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -564,7 +564,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['jsx'] }], parserOptions: jsxParserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, @@ -583,7 +583,7 @@ ruleTester.run('match-component-file-name', rule, { parser: 'vue-eslint-parser', parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -600,7 +600,7 @@ ruleTester.run('match-component-file-name', rule, { parser: 'vue-eslint-parser', parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, @@ -616,7 +616,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -630,7 +630,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -643,7 +643,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -656,7 +656,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -669,7 +669,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] }, { @@ -682,7 +682,7 @@ ruleTester.run('match-component-file-name', rule, { options: [{ extensions: ['js'] }], parserOptions, errors: [{ - message: 'Component name `MComponent` should match file name MyComponent.' + message: 'Component name `MComponent` should match file name `MyComponent`.' }] } ] From 57eb9fb89afe9ae8e1f480a3153da16f486acfc6 Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 11:49:54 -0200 Subject: [PATCH 14/18] improve docs --- docs/rules/match-component-file-name.md | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md index 3782b082c..14aa57a71 100644 --- a/docs/rules/match-component-file-name.md +++ b/docs/rules/match-component-file-name.md @@ -143,11 +143,12 @@ new Vue({ ## :wrench: Options -- `{extensions: ["jsx"]}` (default) ... verify components in files with `.jsx` extension. -- `{extensions: ["vue"]}` (default) ... verify components in files with `.vue` extension. -- `{extensions: ["js"]}` (default) ... verify components in files with `.js` extension. -- `{extensions: ["jsx", "vue"]}` ... verify components in files with `.jsx` or `.vue` extensions. -- `{extensions: ["jsx", "js"]}` ... verify components in files with `.jsx` or `.js` extensions. -- `{extensions: ["vue", "js"]}` ... verify components in files with `.vue` or `.js` extensions. -- `{extensions: ["jsx", "vue", "js"]}` ... verify components in files with any of the - provided extensions. +```json +{ + "vue/match-component-file-name": ["error", { + "extensions": ["jsx"] + }] +} +``` + +- `"extensions": []` ... array of file extensions to be verified. Default is set to ["jsx"] From 81b18fec4ec29b1a985674acf8d4ea10207be78e Mon Sep 17 00:00:00 2001 From: Rodrigo Pedra Brum Date: Sat, 17 Nov 2018 13:31:22 -0200 Subject: [PATCH 15/18] add shouldMatchCase option --- docs/rules/match-component-file-name.md | 53 +++++++++- lib/rules/match-component-file-name.js | 17 +++- tests/lib/rules/match-component-file-name.js | 102 +++++++++++++------ 3 files changed, 135 insertions(+), 37 deletions(-) diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md index 14aa57a71..0d59401ed 100644 --- a/docs/rules/match-component-file-name.md +++ b/docs/rules/match-component-file-name.md @@ -12,7 +12,8 @@ This rule has some options. ```json { "vue/match-component-file-name": ["error", { - "extensions": ["jsx"] + "extensions": ["jsx"], + "shouldMatchCase": false }] } ``` @@ -20,7 +21,9 @@ This rule has some options. 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 with this option. +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. @@ -38,6 +41,7 @@ export default { ```vue // file name: src/MyComponent.vue +// options: {extensions: ["vue"]} + export default { + name: 'my-component', + render() { return
} + } `, - options: [{ extensions: ['vue'] }], - parser: 'vue-eslint-parser', - parserOptions + parserOptions: jsxParserOptions }, { - filename: 'my-component.vue', + filename: 'my-component.jsx', code: ` - + export default { + name: 'MyComponent', + render() { return
} + } `, - options: [{ extensions: ['vue'] }], - parser: 'vue-eslint-parser', - parserOptions + parserOptions: jsxParserOptions }, { - filename: 'my-component.vue', + filename: 'MyComponent.jsx', code: ` - + export default { + name: 'my-component', + render() { return
} + } `, - options: [{ extensions: ['vue'] }], - parser: 'vue-eslint-parser', - parserOptions + parserOptions: jsxParserOptions + }, + { + filename: 'my-component.jsx', + code: ` + export default { + name: 'my-component', + render() { return
} + } + `, + options: [{ shouldMatchCase: true }], + parserOptions: jsxParserOptions + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'MyComponent', + render() { return
} + } + `, + options: [{ shouldMatchCase: true }], + parserOptions: jsxParserOptions } ], @@ -684,6 +694,36 @@ ruleTester.run('match-component-file-name', rule, { errors: [{ message: 'Component name `MComponent` should match file name `MyComponent`.' }] + }, + + // casing + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: 'my-component', + render() { return
} + } + `, + options: [{ shouldMatchCase: true }], + parserOptions: jsxParserOptions, + errors: [{ + message: 'Component name `my-component` should match file name `MyComponent`.' + }] + }, + { + filename: 'my-component.jsx', + code: ` + export default { + name: 'MyComponent', + render() { return
} + } + `, + options: [{ shouldMatchCase: true }], + parserOptions: jsxParserOptions, + errors: [{ + message: 'Component name `MyComponent` should match file name `my-component`.' + }] } ] }) From f5061ad6f5f7b541f806c26a69796d7decf33fb2 Mon Sep 17 00:00:00 2001 From: Armano Date: Sun, 18 Nov 2018 00:24:43 -0200 Subject: [PATCH 16/18] Improve docs Co-Authored-By: rodrigopedra --- docs/rules/match-component-file-name.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/rules/match-component-file-name.md b/docs/rules/match-component-file-name.md index 0d59401ed..679f889f7 100644 --- a/docs/rules/match-component-file-name.md +++ b/docs/rules/match-component-file-name.md @@ -197,3 +197,8 @@ export default { - `"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`. + +## :books: Further reading + + - [Style guide - Single-file component filename casing](https://vuejs.org/v2/style-guide/#Single-file-component-filename-casing-strongly-recommended) + From ef1d19e347a8eecd2d0e11b323cde179bcea7d11 Mon Sep 17 00:00:00 2001 From: ota Date: Sat, 24 Nov 2018 23:17:25 +0900 Subject: [PATCH 17/18] Update match-component-file-name.js --- lib/rules/match-component-file-name.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 0ca3e5c68..67588fd1f 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -21,7 +21,7 @@ module.exports = { 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.4/docs/rules/match-component-file-name.md' + url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.5/docs/rules/match-component-file-name.md' }, fixable: null, schema: [ From 2453ecc0df15a7f38887d28625c04ff887ef287e Mon Sep 17 00:00:00 2001 From: ota Date: Sat, 24 Nov 2018 23:18:43 +0900 Subject: [PATCH 18/18] Update match-component-file-name.js --- lib/rules/match-component-file-name.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/rules/match-component-file-name.js b/lib/rules/match-component-file-name.js index 67588fd1f..b206d46bf 100644 --- a/lib/rules/match-component-file-name.js +++ b/lib/rules/match-component-file-name.js @@ -47,7 +47,7 @@ module.exports = { create (context) { const options = context.options[0] - const shouldMatchCase = options && options.shouldMatchCase || false + const shouldMatchCase = (options && options.shouldMatchCase) || false const extensionsArray = options && options.extensions const allowedExtensions = Array.isArray(extensionsArray) ? extensionsArray : ['jsx']