From 2d4a55ab774e7930cf01b8a47d2ee2e72b1980f2 Mon Sep 17 00:00:00 2001 From: Armano Date: Sat, 24 Nov 2018 21:28:26 +0100 Subject: [PATCH] Add `component-definition-name-casing` rule. --- .../rules/component-definition-name-casing.md | 47 +++ docs/rules/name-property-casing.md | 1 + lib/rules/component-definition-name-casing.js | 101 +++++ lib/rules/name-property-casing.js | 2 + .../rules/component-definition-name-casing.js | 349 ++++++++++++++++++ 5 files changed, 500 insertions(+) create mode 100644 docs/rules/component-definition-name-casing.md create mode 100644 lib/rules/component-definition-name-casing.js create mode 100644 tests/lib/rules/component-definition-name-casing.js diff --git a/docs/rules/component-definition-name-casing.md b/docs/rules/component-definition-name-casing.md new file mode 100644 index 000000000..8bc3f7305 --- /dev/null +++ b/docs/rules/component-definition-name-casing.md @@ -0,0 +1,47 @@ +# enforce specific casing for component definition name (vue/component-definition-name-casing) + +- :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. + +Define a style for component definition name casing for consistency purposes. + +## :book: Rule Details + +:+1: Examples of **correct** code for `PascalCase`: + +```js +export default { + name: 'MyComponent' +} +``` +```js +Vue.component('MyComponent', { + +}) +``` + +:+1: Examples of **correct** code for `kebab-case`: + +```js +export default { + name: 'my-component' +} +``` +```js +Vue.component('my-component', { + +}) +``` + +## :wrench: Options + +Default casing is set to `PascalCase`. + +```json +{ + "vue/component-definition-name-casing": ["error", "PascalCase|kebab-case"] +} +``` + +## Related links + +- [Style guide - Component name casing in JS/JSX](https://vuejs.org/v2/style-guide/#Component-name-casing-in-JS-JSX-strongly-recommended) diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md index c8457af82..e8e4b9747 100644 --- a/docs/rules/name-property-casing.md +++ b/docs/rules/name-property-casing.md @@ -2,6 +2,7 @@ - :gear: This rule is included in `"plugin:vue/strongly-recommended"` and `"plugin:vue/recommended"`. - :wrench: The `--fix` option on the [command line](https://eslint.org/docs/user-guide/command-line-interface#fixing-problems) can automatically fix some of the problems reported by this rule. +- :warning: This rule was **deprecated** and replaced by [vue/component-definition-name-casing](component-definition-name-casing.md) rule. Define a style for the `name` property casing for consistency purposes. diff --git a/lib/rules/component-definition-name-casing.js b/lib/rules/component-definition-name-casing.js new file mode 100644 index 000000000..722a830df --- /dev/null +++ b/lib/rules/component-definition-name-casing.js @@ -0,0 +1,101 @@ +/** + * @fileoverview enforce specific casing for component definition name + * @author Armano + */ +'use strict' + +const utils = require('../utils') +const casing = require('../utils/casing') +const allowedCaseOptions = ['PascalCase', 'kebab-case'] + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + docs: { + description: 'enforce specific casing for component definition name', + category: undefined, + url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.3/docs/rules/component-definition-name-casing.md' + }, + fixable: 'code', // or "code" or "whitespace" + schema: [ + { + enum: allowedCaseOptions + } + ] + }, + + create (context) { + const options = context.options[0] + const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + function convertName (node) { + let nodeValue + let range + if (node.type === 'TemplateLiteral') { + const quasis = node.quasis[0] + nodeValue = quasis.value.cooked + range = quasis.range + } else { + nodeValue = node.value + range = node.range + } + + const value = casing.getConverter(caseType)(nodeValue) + if (value !== nodeValue) { + context.report({ + node: node, + message: 'Property name "{{value}}" is not {{caseType}}.', + data: { + value: nodeValue, + caseType: caseType + }, + fix: fixer => fixer.replaceTextRange([range[0] + 1, range[1] - 1], value) + }) + } + } + + function canConvert (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 (canConvert(argument)) { + convertName(argument) + } + } + } + } + }, + utils.executeOnVue(context, (obj) => { + const node = obj.properties + .find(item => ( + item.type === 'Property' && + item.key.name === 'name' && + canConvert(item.value) + )) + + if (!node) return + convertName(node.value) + }) + ) + } +} diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js index 5de8d0983..368c99a6f 100644 --- a/lib/rules/name-property-casing.js +++ b/lib/rules/name-property-casing.js @@ -20,6 +20,8 @@ module.exports = { category: 'strongly-recommended', url: 'https://github.com/vuejs/eslint-plugin-vue/blob/v5.0.0-beta.5/docs/rules/name-property-casing.md' }, + deprecated: true, + replacedBy: ['component-definition-name-casing'], fixable: 'code', // or "code" or "whitespace" schema: [ { diff --git a/tests/lib/rules/component-definition-name-casing.js b/tests/lib/rules/component-definition-name-casing.js new file mode 100644 index 000000000..c98c82c9a --- /dev/null +++ b/tests/lib/rules/component-definition-name-casing.js @@ -0,0 +1,349 @@ +/** + * @fileoverview enforce specific casing for component definition name + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/component-definition-name-casing') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const parserOptions = { + ecmaVersion: 2018, + sourceType: 'module' +} + +const ruleTester = new RuleTester() +ruleTester.run('component-definition-name-casing', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + } + `, + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + ...name + } + `, + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'FooBar' + } + `, + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'FooBar' + } + `, + options: ['PascalCase'], + parserOptions + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo-bar' + } + `, + options: ['kebab-case'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('FooBar', {})`, + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('FooBar', {})`, + options: ['PascalCase'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('foo-bar', {})`, + options: ['kebab-case'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component(fooBar, {})`, + options: ['kebab-case'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('FooBar', component)`, + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('FooBar', component)`, + options: ['PascalCase'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component('foo-bar', component)`, + options: ['kebab-case'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component(fooBar, component)`, + options: ['kebab-case'], + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.mixin({})`, + parserOptions + }, + { + filename: 'test.vue', + code: `foo({})`, + parserOptions + }, + { + filename: 'test.vue', + code: `foo('foo-bar', {})`, + parserOptions + }, + { + filename: 'test.vue', + code: `Vue.component(\`fooBar\${foo}\`, component)`, + options: ['kebab-case'], + parserOptions + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + name: 'foo-bar' + } + `, + output: ` + export default { + name: 'FooBar' + } + `, + parserOptions, + errors: [{ + message: 'Property name "foo-bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo bar' + } + `, + output: ` + export default { + name: 'FooBar' + } + `, + parserOptions, + errors: [{ + message: 'Property name "foo bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo!bar' + } + `, + output: ` + export default { + name: 'FooBar' + } + `, + parserOptions, + errors: [{ + message: 'Property name "foo!bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.js', + code: ` + new Vue({ + name: 'foo!bar' + }) + `, + output: ` + new Vue({ + name: 'FooBar' + }) + `, + parserOptions: { ecmaVersion: 6 }, + errors: [{ + message: 'Property name "foo!bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo_bar' + } + `, + output: ` + export default { + name: 'FooBar' + } + `, + parserOptions, + errors: [{ + message: 'Property name "foo_bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo_bar' + } + `, + output: ` + export default { + name: 'FooBar' + } + `, + options: ['PascalCase'], + parserOptions, + errors: [{ + message: 'Property name "foo_bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo_bar' + } + `, + output: ` + export default { + name: 'foo-bar' + } + `, + options: ['kebab-case'], + parserOptions, + errors: [{ + message: 'Property name "foo_bar" is not kebab-case.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: `Vue.component('foo-bar', component)`, + output: `Vue.component('FooBar', component)`, + parserOptions, + errors: [{ + message: 'Property name "foo-bar" is not PascalCase.', + type: 'Literal', + line: 1 + }] + }, + { + filename: 'test.vue', + code: `(Vue as VueConstructor).component('foo-bar', component)`, + output: `(Vue as VueConstructor).component('FooBar', component)`, + parserOptions, + parser: 'typescript-eslint-parser', + errors: [{ + message: 'Property name "foo-bar" is not PascalCase.', + type: 'Literal', + line: 1 + }] + }, + { + filename: 'test.vue', + code: `Vue.component('foo-bar', {})`, + output: `Vue.component('FooBar', {})`, + parserOptions, + errors: [{ + message: 'Property name "foo-bar" is not PascalCase.', + type: 'Literal', + line: 1 + }] + }, + { + filename: 'test.js', + code: `Vue.component('foo_bar', {})`, + output: `Vue.component('FooBar', {})`, + options: ['PascalCase'], + parserOptions, + errors: [{ + message: 'Property name "foo_bar" is not PascalCase.', + type: 'Literal', + line: 1 + }] + }, + { + filename: 'test.vue', + code: `Vue.component('foo_bar', {})`, + output: `Vue.component('foo-bar', {})`, + options: ['kebab-case'], + parserOptions, + errors: [{ + message: 'Property name "foo_bar" is not kebab-case.', + type: 'Literal', + line: 1 + }] + }, + { + filename: 'test.vue', + code: `Vue.component(\`foo_bar\`, {})`, + output: `Vue.component(\`foo-bar\`, {})`, + options: ['kebab-case'], + parserOptions, + errors: [{ + message: 'Property name "foo_bar" is not kebab-case.', + type: 'TemplateLiteral', + line: 1 + }] + } + ] +})