diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md new file mode 100644 index 000000000..316cda8f3 --- /dev/null +++ b/docs/rules/name-property-casing.md @@ -0,0 +1,37 @@ +# Requires specific casing for the name property in Vue components (name-property-casing) + +Define a style for the `name` property casing for consistency purposes. + +## :book: Rule Details + +:+1: Examples of **correct** code for `PascalCase`: + +```js +export default { + name: 'MyComponent' +} +``` + +:+1: Examples of **correct** code for `kebab-case`: + +```js +export default { + name: 'my-component' +} +``` + +:+1: Examples of **correct** code for `camelCase`: + +```js +export default { + name: 'myComponent' +} +``` + +## :wrench: Options + +Default casing is set to `PascalCase` + +``` +'vue/name-property-casing': [2, 'camelCase|kebab-case|PascalCase'] +``` diff --git a/lib/rules/name-property-casing.js b/lib/rules/name-property-casing.js new file mode 100644 index 000000000..31a201134 --- /dev/null +++ b/lib/rules/name-property-casing.js @@ -0,0 +1,99 @@ +/** + * @fileoverview Requires specific casing for the name property in Vue components + * @author Armano + */ +'use strict' + +const utils = require('../utils') + +function kebabCase (str) { + return str + .replace(/([a-z])([A-Z])/g, match => match[0] + '-' + match[1]) + .replace(/[^a-zA-Z:]+/g, '-') + .toLowerCase() +} + +function camelCase (str) { + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => ( + index === 0 ? letter.toLowerCase() : letter.toUpperCase()) + ) + .replace(/[^a-zA-Z:]+/g, '') +} + +function pascalCase (str) { + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => letter.toUpperCase()) + .replace(/[^a-zA-Z:]+/g, '') +} + +const allowedCaseOptions = [ + 'camelCase', + 'kebab-case', + 'PascalCase' +] + +const convertersMap = { + 'kebab-case': kebabCase, + 'camelCase': camelCase, + 'PascalCase': pascalCase +} + +function getConverter (name) { + return convertersMap[name] || pascalCase +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function create (context) { + const options = context.options[0] + const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' + + // ---------------------------------------------------------------------- + // Public + // ---------------------------------------------------------------------- + + return utils.executeOnVueComponent(context, (obj) => { + const node = obj.properties + .filter(item => ( + item.type === 'Property' && + item.key.name === 'name' && + item.value.type === 'Literal' + ))[0] + + if (!node) return + + const value = getConverter(caseType)(node.value.value) + if (value !== node.value.value) { + context.report({ + node: node.value, + message: 'Property name "{{value}}" is not {{caseType}}.', + data: { + value: node.value.value, + caseType: caseType + }, + fix: fixer => fixer.replaceText(node.value, node.value.raw.replace(node.value.value, value)) + }) + } + }) +} + +module.exports = { + meta: { + docs: { + description: 'Requires specific casing for the name property in Vue components', + category: 'Stylistic Issues', + recommended: false + }, + fixable: 'code', // or "code" or "whitespace" + schema: [ + { + enum: allowedCaseOptions + } + ] + }, + + create +} diff --git a/tests/lib/rules/name-property-casing.js b/tests/lib/rules/name-property-casing.js new file mode 100644 index 000000000..07df15089 --- /dev/null +++ b/tests/lib/rules/name-property-casing.js @@ -0,0 +1,170 @@ +/** + * @fileoverview Define a style for the name property casing for consistency purposes + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/name-property-casing') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('name-property-casing', rule, { + + valid: [ + { + filename: 'test.vue', + code: ` + export default { + } + `, + options: ['camelCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' } + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'fooBar' + } + `, + options: ['camelCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' } + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'FooBar' + } + `, + options: ['PascalCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' } + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo-bar' + } + `, + options: ['kebab-case'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' } + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + name: 'foo-bar' + } + `, + options: ['camelCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Property name "foo-bar" is not camelCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo bar' + } + `, + options: ['PascalCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Property name "foo bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo!bar' + } + `, + options: ['camelCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Property name "foo!bar" is not camelCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.js', + code: ` + new Vue({ + name: 'foo!bar' + }) + `, + options: ['camelCase'], + parserOptions: { ecmaVersion: 6 }, + errors: [{ + message: 'Property name "foo!bar" is not camelCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo_bar' + } + `, + options: ['camelCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Property name "foo_bar" is not camelCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo_bar' + } + `, + options: ['PascalCase'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Property name "foo_bar" is not PascalCase.', + type: 'Literal', + line: 3 + }] + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'foo_bar' + } + `, + options: ['kebab-case'], + parserOptions: { ecmaVersion: 6, sourceType: 'module' }, + errors: [{ + message: 'Property name "foo_bar" is not kebab-case.', + type: 'Literal', + line: 3 + }] + } + ] +})