From 850297138880291b7b57512dbf4396e808e0973a Mon Sep 17 00:00:00 2001 From: Armano Date: Wed, 19 Jul 2017 19:50:33 +0200 Subject: [PATCH 1/3] Add `name-casing` rule. --- docs/rules/name-casing.md | 37 ++++++++++ lib/rules/name-casing.js | 79 +++++++++++++++++++++ tests/lib/rules/name-casing.js | 125 +++++++++++++++++++++++++++++++++ 3 files changed, 241 insertions(+) create mode 100644 docs/rules/name-casing.md create mode 100644 lib/rules/name-casing.js create mode 100644 tests/lib/rules/name-casing.js diff --git a/docs/rules/name-casing.md b/docs/rules/name-casing.md new file mode 100644 index 000000000..7fb279b17 --- /dev/null +++ b/docs/rules/name-casing.md @@ -0,0 +1,37 @@ +# Name property casing for consistency purposes (name-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-casing': [2, 'camelCase'|'kebab-case'|'PascalCase'] +``` diff --git a/lib/rules/name-casing.js b/lib/rules/name-casing.js new file mode 100644 index 000000000..f930f6dc9 --- /dev/null +++ b/lib/rules/name-casing.js @@ -0,0 +1,79 @@ +/** + * @fileoverview Name property casing for consistency purposes + * @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(/\s+/g, '-').toLowerCase() +} + +function camelCase (str) { + return str.replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => index === 0 ? letter.toLowerCase() : letter.toUpperCase()).replace(/[\s-]+/g, '') +} + +function pascalCase (str) { + str = camelCase(str) + return str.length > 0 ? str.charAt(0).toUpperCase() + str.slice(1) : '' +} + +function convertCase (str, caseType) { + if (caseType === 'kebab-case') { + return kebabCase(str) + } else if (caseType === 'PascalCase') { + return pascalCase(str) + } + return camelCase(str) +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +function create (context) { + const options = context.options[0] + const caseType = ['camelCase', 'kebab-case', 'PascalCase'].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) { + const value = convertCase(node.value.value, caseType) + 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: 'Name property casing for consistency purposes', + category: 'Stylistic Issues', + recommended: false + }, + fixable: 'code', // or "code" or "whitespace" + schema: [ + { + enum: ['camelCase', 'kebab-case', 'PascalCase'] + } + ] + }, + + create +} diff --git a/tests/lib/rules/name-casing.js b/tests/lib/rules/name-casing.js new file mode 100644 index 000000000..ea24f46f9 --- /dev/null +++ b/tests/lib/rules/name-casing.js @@ -0,0 +1,125 @@ +/** + * @fileoverview Define a style for the name property casing for consistency purposes + * @author Armano + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/name-casing') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester() +ruleTester.run('name-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 + }] + } + ] +}) From 2f063d16f64a754dde6f280bbb246536cc0d6dd7 Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 21 Jul 2017 00:28:42 +0200 Subject: [PATCH 2/3] rename rule name-casing -> name-property-casing --- docs/rules/{name-casing.md => name-property-casing.md} | 4 ++-- lib/rules/{name-casing.js => name-property-casing.js} | 0 tests/lib/rules/{name-casing.js => name-property-casing.js} | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) rename docs/rules/{name-casing.md => name-property-casing.md} (77%) rename lib/rules/{name-casing.js => name-property-casing.js} (100%) rename tests/lib/rules/{name-casing.js => name-property-casing.js} (96%) diff --git a/docs/rules/name-casing.md b/docs/rules/name-property-casing.md similarity index 77% rename from docs/rules/name-casing.md rename to docs/rules/name-property-casing.md index 7fb279b17..9c59d29dd 100644 --- a/docs/rules/name-casing.md +++ b/docs/rules/name-property-casing.md @@ -1,4 +1,4 @@ -# Name property casing for consistency purposes (name-casing) +# Name property casing for consistency purposes (name-property-casing) Define a style for the `name` property casing for consistency purposes @@ -33,5 +33,5 @@ export default { Default casing is set to `PascalCase` ``` -'vue/name-casing': [2, 'camelCase'|'kebab-case'|'PascalCase'] +'vue/name-property-casing': [2, 'camelCase'|'kebab-case'|'PascalCase'] ``` diff --git a/lib/rules/name-casing.js b/lib/rules/name-property-casing.js similarity index 100% rename from lib/rules/name-casing.js rename to lib/rules/name-property-casing.js diff --git a/tests/lib/rules/name-casing.js b/tests/lib/rules/name-property-casing.js similarity index 96% rename from tests/lib/rules/name-casing.js rename to tests/lib/rules/name-property-casing.js index ea24f46f9..cef2a54dc 100644 --- a/tests/lib/rules/name-casing.js +++ b/tests/lib/rules/name-property-casing.js @@ -8,7 +8,7 @@ // Requirements // ------------------------------------------------------------------------------ -const rule = require('../../../lib/rules/name-casing') +const rule = require('../../../lib/rules/name-property-casing') const RuleTester = require('eslint').RuleTester // ------------------------------------------------------------------------------ @@ -16,7 +16,7 @@ const RuleTester = require('eslint').RuleTester // ------------------------------------------------------------------------------ const ruleTester = new RuleTester() -ruleTester.run('name-casing', rule, { +ruleTester.run('name-property-casing', rule, { valid: [ { From cd6f6c35580ff40f3a3b373f7d92daac94a9595c Mon Sep 17 00:00:00 2001 From: Armano Date: Fri, 21 Jul 2017 16:09:11 +0200 Subject: [PATCH 3/3] Apply changes requested in code review. --- docs/rules/name-property-casing.md | 6 +- lib/rules/name-property-casing.js | 78 ++++++++++++++++--------- tests/lib/rules/name-property-casing.js | 45 ++++++++++++++ 3 files changed, 97 insertions(+), 32 deletions(-) diff --git a/docs/rules/name-property-casing.md b/docs/rules/name-property-casing.md index 9c59d29dd..316cda8f3 100644 --- a/docs/rules/name-property-casing.md +++ b/docs/rules/name-property-casing.md @@ -1,6 +1,6 @@ -# Name property casing for consistency purposes (name-property-casing) +# Requires specific casing for the name property in Vue components (name-property-casing) -Define a style for the `name` property casing for consistency purposes +Define a style for the `name` property casing for consistency purposes. ## :book: Rule Details @@ -33,5 +33,5 @@ export default { Default casing is set to `PascalCase` ``` -'vue/name-property-casing': [2, 'camelCase'|'kebab-case'|'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 index f930f6dc9..31a201134 100644 --- a/lib/rules/name-property-casing.js +++ b/lib/rules/name-property-casing.js @@ -1,5 +1,5 @@ /** - * @fileoverview Name property casing for consistency purposes + * @fileoverview Requires specific casing for the name property in Vue components * @author Armano */ 'use strict' @@ -7,25 +7,40 @@ const utils = require('../utils') function kebabCase (str) { - return str.replace(/([a-z])([A-Z])/g, match => match[0] + '-' + match[1]).replace(/\s+/g, '-').toLowerCase() + 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(/[\s-]+/g, '') + 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) { - str = camelCase(str) - return str.length > 0 ? str.charAt(0).toUpperCase() + str.slice(1) : '' + return str + .replace(/(?:^\w|[A-Z]|\b\w)/g, (letter, index) => letter.toUpperCase()) + .replace(/[^a-zA-Z:]+/g, '') } -function convertCase (str, caseType) { - if (caseType === 'kebab-case') { - return kebabCase(str) - } else if (caseType === 'PascalCase') { - return pascalCase(str) - } - return camelCase(str) +const allowedCaseOptions = [ + 'camelCase', + 'kebab-case', + 'PascalCase' +] + +const convertersMap = { + 'kebab-case': kebabCase, + 'camelCase': camelCase, + 'PascalCase': pascalCase +} + +function getConverter (name) { + return convertersMap[name] || pascalCase } // ------------------------------------------------------------------------------ @@ -34,7 +49,7 @@ function convertCase (str, caseType) { function create (context) { const options = context.options[0] - const caseType = ['camelCase', 'kebab-case', 'PascalCase'].indexOf(options) !== -1 ? options : 'PascalCase' + const caseType = allowedCaseOptions.indexOf(options) !== -1 ? options : 'PascalCase' // ---------------------------------------------------------------------- // Public @@ -42,20 +57,25 @@ function create (context) { 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) { - const value = convertCase(node.value.value, caseType) - 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)) - }) - } + .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)) + }) } }) } @@ -63,14 +83,14 @@ function create (context) { module.exports = { meta: { docs: { - description: 'Name property casing for consistency purposes', + description: 'Requires specific casing for the name property in Vue components', category: 'Stylistic Issues', recommended: false }, fixable: 'code', // or "code" or "whitespace" schema: [ { - enum: ['camelCase', 'kebab-case', 'PascalCase'] + enum: allowedCaseOptions } ] }, diff --git a/tests/lib/rules/name-property-casing.js b/tests/lib/rules/name-property-casing.js index cef2a54dc..07df15089 100644 --- a/tests/lib/rules/name-property-casing.js +++ b/tests/lib/rules/name-property-casing.js @@ -120,6 +120,51 @@ ruleTester.run('name-property-casing', rule, { 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 + }] } ] })