From a7c23586946dc0df1fc90ea8ae86c5e46251d4c1 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Fri, 7 Dec 2018 01:53:10 +0900 Subject: [PATCH 1/6] Add `registeredComponentsOnly` option to `component-name-in-template-casing` rule --- .../component-name-in-template-casing.md | 143 +++++++- .../component-name-in-template-casing.js | 81 +++- tests/lib/autofix.js | 4 +- .../component-name-in-template-casing.js | 345 ++++++++++++++++-- 4 files changed, 515 insertions(+), 58 deletions(-) diff --git a/docs/rules/component-name-in-template-casing.md b/docs/rules/component-name-in-template-casing.md index c7ea7ea84..ab367b47c 100644 --- a/docs/rules/component-name-in-template-casing.md +++ b/docs/rules/component-name-in-template-casing.md @@ -20,7 +20,10 @@ This rule aims to warn the tag names other than the configured casing in Vue.js ```json { - "vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", { + "vue/component-name-in-template-casing": ["error", "PascalCase" | "kebab-case", { + "registeredComponentsOnly": true, + "globalRegisteredComponents": [], + "globalRegisteredComponentPatterns": [], "ignores": [] }] } @@ -28,55 +31,165 @@ This rule aims to warn the tag names other than the configured casing in Vue.js - `"PascalCase"` (default) ... enforce tag names to pascal case. E.g. ``. This is consistent with the JSX practice. - `"kebab-case"` ... enforce tag names to kebab case: E.g. ``. This is consistent with the HTML practice which is case-insensitive originally. +- `registeredComponentsOnly` ... If `true`, only registered components are checked. If `false`, check all. + default `true` +- `globalRegisteredComponents` (`string[]`) ... (Only available when `registeredComponentsOnly` is `true`) The name of globally registered components. +- `globalRegisteredComponentPatterns` (`string[]`) ... (Only available when `registeredComponentsOnly` is `true`) The pattern of the names of globally registered components. + default `true` - `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, a custom element or a non-Vue component. -### `"PascalCase"` +### `"PascalCase", { registeredComponentsOnly: true }` (default) -``` + +```html + ``` + ### `"kebab-case"` + +``` + + +``` + + + +### `"PascalCase", { globalRegisteredComponents: ["GlobalComponent"] }` + + + +```html + + ``` + + + +### `"PascalCase", { globalRegisteredComponentPatterns: ["^Global"] }` + + + +```html + +``` + + +### `"PascalCase", { registeredComponentsOnly: false }` + + + +```html + + ``` + +### `"PascalCase", { ignores: ["custom-element"], registeredComponentsOnly: false }` -### `"PascalCase", { ignores: ["custom-element"] }` + - ``` ``` + ## :books: Further reading diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js index c48ba50ce..c7ff19449 100644 --- a/lib/rules/component-name-in-template-casing.js +++ b/lib/rules/component-name-in-template-casing.js @@ -39,6 +39,21 @@ module.exports = { items: { type: 'string' }, uniqueItems: true, additionalItems: false + }, + registeredComponentsOnly: { + type: 'boolean' + }, + globalRegisteredComponents: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + additionalItems: false + }, + globalRegisteredComponentPatterns: { + type: 'array', + items: { type: 'string' }, + uniqueItems: true, + additionalItems: false } }, additionalProperties: false @@ -51,8 +66,47 @@ module.exports = { const options = context.options[1] || {} const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase const ignores = options.ignores || [] + const registeredComponentsOnly = options.registeredComponentsOnly !== false + const registeredComponents = options.globalRegisteredComponents || [] + const globalRegisteredComponentPatterns = (options.globalRegisteredComponentPatterns || []).map(s => new RegExp(s)) const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() + /** + * Checks whether the given node is the verification target node. + * @param {VElement} node element node + * @returns {boolean} `true` if the given node is the verification target node. + */ + function isVerifyTarget (node) { + if (ignores.indexOf(node.rawName) >= 0) { + // ignore + return false + } + + if (!registeredComponentsOnly) { + // If the user specifies registeredComponentsOnly as false, it checks all component tags. + if ((!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || + utils.isHtmlWellKnownElementName(node.rawName) || + utils.isSvgWellKnownElementName(node.rawName) + ) { + return false + } + return true + } + + // We only verify the components registered in the component. + if (registeredComponents + .filter(name => casing.pascalCase(name) === name) // When defining a component with PascalCase, you can use either case + .some(name => node.rawName === name || casing.pascalCase(node.rawName) === name)) { + return true + } + + if (globalRegisteredComponentPatterns + .some(re => node.rawName.match(re) || casing.pascalCase(node.rawName).match(re))) { + return true + } + return false + } + let hasInvalidEOF = false return utils.defineTemplateBodyVisitor(context, { @@ -61,18 +115,11 @@ module.exports = { return } - if ( - (!utils.isHtmlElementNode(node) && !utils.isSvgElementNode(node)) || - utils.isHtmlWellKnownElementName(node.rawName) || - utils.isSvgWellKnownElementName(node.rawName) - ) { + if (!isVerifyTarget(node)) { return } const name = node.rawName - if (ignores.indexOf(name) >= 0) { - return - } const casingName = casing.getConverter(caseType)(name) if (casingName !== name) { const startTag = node.startTag @@ -100,10 +147,18 @@ module.exports = { }) } } - }, { - Program (node) { - hasInvalidEOF = utils.hasInvalidEOF(node) - } - }) + }, + Object.assign( + { + Program (node) { + hasInvalidEOF = utils.hasInvalidEOF(node) + } + }, + registeredComponentsOnly + ? utils.executeOnVue(context, (obj) => { + registeredComponents.push(...utils.getRegisteredComponents(obj).map(n => n.name)) + }) + : {} + )) } } diff --git a/tests/lib/autofix.js b/tests/lib/autofix.js index 5a38dd0e4..6cdedc113 100644 --- a/tests/lib/autofix.js +++ b/tests/lib/autofix.js @@ -95,7 +95,7 @@ describe('Complex autofix test cases', () => { 'component': 'never' } }], - 'vue/component-name-in-template-casing': ['error', 'kebab-case'] + 'vue/component-name-in-template-casing': ['error', 'kebab-case', { registeredComponentsOnly: false }] }}) const pascalConfig = Object.assign({}, baseConfig, { 'rules': { @@ -104,7 +104,7 @@ describe('Complex autofix test cases', () => { 'component': 'never' } }], - 'vue/component-name-in-template-casing': ['error'] + 'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }] }}) it('Even if set kebab-case, the output should be as expected.', () => { diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js index 8dcd3937f..3119a6c0f 100644 --- a/tests/lib/rules/component-name-in-template-casing.js +++ b/tests/lib/rules/component-name-in-template-casing.js @@ -15,59 +15,214 @@ const RuleTester = require('eslint').RuleTester // ------------------------------------------------------------------------------ const tester = new RuleTester({ - parser: 'vue-eslint-parser' + parser: 'vue-eslint-parser', + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + } }) tester.run('component-name-in-template-casing', rule, { valid: [ // default - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', - '', + { + code: ` + + + `, + filename: 'test.vue' + }, + + // element types test + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, // kebab-case { code: '', - options: ['kebab-case'] + options: ['kebab-case', { registeredComponentsOnly: false }] }, { code: '', - options: ['kebab-case'] + options: ['kebab-case', { registeredComponentsOnly: false }] }, { code: '', - options: ['kebab-case'] + options: ['kebab-case', { registeredComponentsOnly: false }] }, { code: '', - options: ['kebab-case'] + options: ['kebab-case', { registeredComponentsOnly: false }] }, { code: '', - options: ['kebab-case'] + options: ['kebab-case', { registeredComponentsOnly: false }] + }, + + // globalRegisteredComponents + { + code: ` + + `, + filename: 'test.vue', + options: ['PascalCase', { globalRegisteredComponents: ['GlobalButton', 'GlobalCard', 'GlobalGrid'] }] }, + + // globalRegisteredComponentPatterns + { + code: ` + + `, + filename: 'test.vue', + options: ['PascalCase', { globalRegisteredComponentPatterns: ['^Global'] }] + }, + // ignores { code: '', - options: ['PascalCase', { ignores: ['custom-element'] }] + options: ['PascalCase', { ignores: ['custom-element'], registeredComponentsOnly: false }] }, { code: '', - options: ['PascalCase', { ignores: ['custom-element'] }] + options: ['PascalCase', { ignores: ['custom-element'], registeredComponentsOnly: false }] }, // Invalid EOF - '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { code: ' + + `, + filename: 'test.vue', + output: ` + + + `, + errors: [ + { + message: 'Component name "cool-component" is not PascalCase.', + line: 4, + column: 11, + endLine: 4, + endColumn: 26 + }, + { + message: 'Component name "coolComponent" is not PascalCase.', + line: 5, + column: 11, + endLine: 5, + endColumn: 25 + }, + { + message: 'Component name "Cool-component" is not PascalCase.', + line: 6, + column: 11, + endLine: 6, + endColumn: 26 + } + ] + }, + { + code: ` + + + `, + filename: 'test.vue', + options: ['kebab-case'], + output: ` + + + `, + errors: [ + { + message: 'Component name "CoolComponent" is not kebab-case.', + line: 4 + }, + { + message: 'Component name "coolComponent" is not kebab-case.', + line: 5 + }, + { + message: 'Component name "Cool-component" is not kebab-case.', + line: 6 + } + ] + }, { code: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` + `, + filename: 'test.vue', options: ['kebab-case'], output: ` + `, errors: ['Component name "TheComponent" is not kebab-case.'] }, @@ -156,7 +334,7 @@ tester.run('component-name-in-template-casing', rule, { `, - options: ['kebab-case'], + options: ['kebab-case', { registeredComponentsOnly: false }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, - options: ['kebab-case'], + options: ['kebab-case', { globalRegisteredComponents: ['TheComponent'] }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, - options: ['kebab-case'], + options: ['kebab-case', { globalRegisteredComponentPatterns: ['Component$'] }], output: ` `, - options: ['kebab-case'], + options: ['kebab-case', { registeredComponentsOnly: false }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, + options: ['PascalCase', { + globalRegisteredComponents: ['TheComponent'] + }], output: ` `, output: ` `, - options: ['PascalCase', { ignores: ['custom-element'] }], - errors: ['Component name "the-component" is not PascalCase.'] + options: ['PascalCase', { + ignores: ['custom-element'], + globalRegisteredComponentPatterns: ['^the', '^custom'] + }], + errors: [ + 'Component name "the-component" is not PascalCase.', + 'Component name "custom-component" is not PascalCase.' + ] + }, + { + code: ` + `, + output: ` + `, + options: ['PascalCase', { + ignores: ['custom-element1', 'custom-element2'], + globalRegisteredComponentPatterns: ['^the', '^custom'] + }], + errors: [ + 'Component name "the-component" is not PascalCase.', + 'Component name "the-component" is not PascalCase.' + ] }, { code: ` @@ -349,7 +635,10 @@ tester.run('component-name-in-template-casing', rule, { `, - options: ['PascalCase', { ignores: ['custom-element1', 'custom-element2'] }], + options: ['PascalCase', { + ignores: ['custom-element1', 'custom-element2'], + registeredComponentsOnly: false + }], errors: [ 'Component name "the-component" is not PascalCase.', 'Component name "the-component" is not PascalCase.' From d8ec797f9829f2ab6bae8349342e8f7a961657b7 Mon Sep 17 00:00:00 2001 From: ota Date: Fri, 7 Dec 2018 09:23:31 +0900 Subject: [PATCH 2/6] update doc --- docs/rules/component-name-in-template-casing.md | 1 - 1 file changed, 1 deletion(-) diff --git a/docs/rules/component-name-in-template-casing.md b/docs/rules/component-name-in-template-casing.md index ab367b47c..e3de14f86 100644 --- a/docs/rules/component-name-in-template-casing.md +++ b/docs/rules/component-name-in-template-casing.md @@ -35,7 +35,6 @@ This rule aims to warn the tag names other than the configured casing in Vue.js default `true` - `globalRegisteredComponents` (`string[]`) ... (Only available when `registeredComponentsOnly` is `true`) The name of globally registered components. - `globalRegisteredComponentPatterns` (`string[]`) ... (Only available when `registeredComponentsOnly` is `true`) The pattern of the names of globally registered components. - default `true` - `ignores` (`string[]`) ... The element names to ignore. Sets the element name to allow. For example, a custom element or a non-Vue component. ### `"PascalCase", { registeredComponentsOnly: true }` (default) From ac697f95018039ed1fef4bbd802d38387d001474 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 2 Jan 2019 18:49:29 +0900 Subject: [PATCH 3/6] no message --- .../component-name-in-template-casing.js | 49 +-- .../component-name-in-template-casing.js | 315 ++++++++++-------- 2 files changed, 207 insertions(+), 157 deletions(-) diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js index c7ff19449..6d747488a 100644 --- a/lib/rules/component-name-in-template-casing.js +++ b/lib/rules/component-name-in-template-casing.js @@ -11,9 +11,33 @@ const utils = require('../utils') const casing = require('../utils/casing') +// ----------------------------------------------------------------------------- +// Helpers +// ----------------------------------------------------------------------------- + const allowedCaseOptions = ['PascalCase', 'kebab-case'] const defaultCase = 'PascalCase' +const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u +const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/g +const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source) + +function escapeRegExp (string) { + return (string && RE_HAS_REGEXP_CHAR.test(string)) + ? string.replace(RE_REGEXP_CHAR, '\\$&') + : string +} + +function toRegExpList (array) { + return array.map(str => { + const parts = RE_REGEXP_STR.exec(str) + if (parts) { + return new RegExp(parts[1], parts[2]) + } + return new RegExp(`^${escapeRegExp(str)}$`) + }) +} + // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -42,18 +66,6 @@ module.exports = { }, registeredComponentsOnly: { type: 'boolean' - }, - globalRegisteredComponents: { - type: 'array', - items: { type: 'string' }, - uniqueItems: true, - additionalItems: false - }, - globalRegisteredComponentPatterns: { - type: 'array', - items: { type: 'string' }, - uniqueItems: true, - additionalItems: false } }, additionalProperties: false @@ -65,19 +77,19 @@ module.exports = { const caseOption = context.options[0] const options = context.options[1] || {} const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase - const ignores = options.ignores || [] + const ignores = toRegExpList(options.ignores || []) const registeredComponentsOnly = options.registeredComponentsOnly !== false - const registeredComponents = options.globalRegisteredComponents || [] - const globalRegisteredComponentPatterns = (options.globalRegisteredComponentPatterns || []).map(s => new RegExp(s)) const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() + const registeredComponents = [] + /** * Checks whether the given node is the verification target node. * @param {VElement} node element node * @returns {boolean} `true` if the given node is the verification target node. */ function isVerifyTarget (node) { - if (ignores.indexOf(node.rawName) >= 0) { + if (ignores.some(re => re.test(node.rawName))) { // ignore return false } @@ -92,7 +104,6 @@ module.exports = { } return true } - // We only verify the components registered in the component. if (registeredComponents .filter(name => casing.pascalCase(name) === name) // When defining a component with PascalCase, you can use either case @@ -100,10 +111,6 @@ module.exports = { return true } - if (globalRegisteredComponentPatterns - .some(re => node.rawName.match(re) || casing.pascalCase(node.rawName).match(re))) { - return true - } return false } diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js index 3119a6c0f..cc60b4cb5 100644 --- a/tests/lib/rules/component-name-in-template-casing.js +++ b/tests/lib/rules/component-name-in-template-casing.js @@ -79,20 +79,16 @@ tester.run('component-name-in-template-casing', rule, { options: ['kebab-case', { registeredComponentsOnly: false }] }, - // globalRegisteredComponents + // ignores { - code: ` - - `, - filename: 'test.vue', - options: ['PascalCase', { globalRegisteredComponents: ['GlobalButton', 'GlobalCard', 'GlobalGrid'] }] + code: '', + options: ['PascalCase', { ignores: ['custom-element'], registeredComponentsOnly: false }] }, - - // globalRegisteredComponentPatterns + { + code: '', + options: ['PascalCase', { ignores: ['custom-element'], registeredComponentsOnly: false }] + }, + // regexp ignores { code: ` `, filename: 'test.vue', - options: ['PascalCase', { globalRegisteredComponentPatterns: ['^Global'] }] + options: ['PascalCase', { registeredComponentsOnly: false, ignores: ['/^Global/'] }] }, - // ignores - { - code: '', - options: ['PascalCase', { ignores: ['custom-element'], registeredComponentsOnly: false }] - }, - { - code: '', - options: ['PascalCase', { ignores: ['custom-element'], registeredComponentsOnly: false }] - }, // Invalid EOF { code: ' + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -270,16 +275,25 @@ tester.run('component-name-in-template-casing', rule, { content + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -288,14 +302,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -348,15 +371,24 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -365,14 +397,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -381,14 +422,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -397,14 +447,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "theComponent" is not PascalCase.'] }, @@ -413,12 +472,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['kebab-case', { globalRegisteredComponents: ['TheComponent'] }], + filename: 'test.vue', + options: ['kebab-case'], output: ` + `, errors: ['Component name "theComponent" is not kebab-case.'] }, @@ -427,14 +497,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "The-component" is not PascalCase.'] }, @@ -443,12 +522,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['kebab-case', { globalRegisteredComponentPatterns: ['Component$'] }], + filename: 'test.vue', + options: ['kebab-case'], output: ` + `, errors: ['Component name "The-component" is not kebab-case.'] }, @@ -471,14 +561,23 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -488,15 +587,24 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, @@ -505,93 +613,28 @@ tester.run('component-name-in-template-casing', rule, { + `, - options: ['PascalCase', { - globalRegisteredComponents: ['TheComponent'] - }], + filename: 'test.vue', + options: ['PascalCase'], output: ` + `, errors: ['Component name "the-component" is not PascalCase.'] }, - // globalRegisteredComponents - { - code: ` - - `, - filename: 'test.vue', - options: ['PascalCase', { globalRegisteredComponents: ['GlobalButton', 'GlobalCard', 'GlobalGrid'] }], - output: ` - - `, - errors: [ - 'Component name "global-button" is not PascalCase.', - 'Component name "global-card" is not PascalCase.', - 'Component name "global-grid" is not PascalCase.' - ] - }, - - // globalRegisteredComponentPatterns - { - code: ` - - `, - filename: 'test.vue', - options: ['PascalCase', { globalRegisteredComponentPatterns: ['^Global'] }], - output: ` - - `, - errors: [ - 'Component name "global-button" is not PascalCase.', - 'Component name "global-card" is not PascalCase.', - 'Component name "global-grid" is not PascalCase.' - ] - }, - // ignores - { - code: ` - `, - output: ` - `, - options: ['PascalCase', { - ignores: ['custom-element'], - globalRegisteredComponentPatterns: ['^the', '^custom'] - }], - errors: [ - 'Component name "the-component" is not PascalCase.', - 'Component name "custom-component" is not PascalCase.' - ] - }, { code: ` `, options: ['PascalCase', { ignores: ['custom-element1', 'custom-element2'], - globalRegisteredComponentPatterns: ['^the', '^custom'] + registeredComponentsOnly: false }], errors: [ 'Component name "the-component" is not PascalCase.', @@ -636,7 +679,7 @@ tester.run('component-name-in-template-casing', rule, { `, options: ['PascalCase', { - ignores: ['custom-element1', 'custom-element2'], + ignores: ['/^custom-element/'], registeredComponentsOnly: false }], errors: [ From e57797724223e936e4fa44a17fbf4b86a1219cce Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Wed, 2 Jan 2019 20:24:28 +0900 Subject: [PATCH 4/6] update test case --- tests/lib/rules/component-name-in-template-casing.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js index cc60b4cb5..643bb1191 100644 --- a/tests/lib/rules/component-name-in-template-casing.js +++ b/tests/lib/rules/component-name-in-template-casing.js @@ -92,13 +92,13 @@ tester.run('component-name-in-template-casing', rule, { { code: ` `, filename: 'test.vue', - options: ['PascalCase', { registeredComponentsOnly: false, ignores: ['/^Global/'] }] + options: ['PascalCase', { registeredComponentsOnly: false, ignores: ['/^global/'] }] }, // Invalid EOF From 3c7254a96757be3acacf3960581f6831dd18020f Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 3 Jan 2019 09:47:42 +0900 Subject: [PATCH 5/6] Added test cases of RegExp utils. --- .../component-name-in-template-casing.js | 22 +--------- lib/utils/regexp.js | 38 ++++++++++++++++++ tests/lib/utils/regexp.js | 40 +++++++++++++++++++ 3 files changed, 80 insertions(+), 20 deletions(-) create mode 100644 lib/utils/regexp.js create mode 100644 tests/lib/utils/regexp.js diff --git a/lib/rules/component-name-in-template-casing.js b/lib/rules/component-name-in-template-casing.js index b44b43d98..8cd7a6737 100644 --- a/lib/rules/component-name-in-template-casing.js +++ b/lib/rules/component-name-in-template-casing.js @@ -10,6 +10,7 @@ const utils = require('../utils') const casing = require('../utils/casing') +const { toRegExp } = require('../utils/regexp') // ----------------------------------------------------------------------------- // Helpers @@ -18,25 +19,6 @@ const casing = require('../utils/casing') const allowedCaseOptions = ['PascalCase', 'kebab-case'] const defaultCase = 'PascalCase' -const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu -const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source) -function escapeRegExp (string) { - return (string && RE_HAS_REGEXP_CHAR.test(string)) - ? string.replace(RE_REGEXP_CHAR, '\\$&') - : string -} - -const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u -function toRegExpList (array) { - return array.map(str => { - const parts = RE_REGEXP_STR.exec(str) - if (parts) { - return new RegExp(parts[1], parts[2]) - } - return new RegExp(`^${escapeRegExp(str)}$`) - }) -} - // ------------------------------------------------------------------------------ // Rule Definition // ------------------------------------------------------------------------------ @@ -76,7 +58,7 @@ module.exports = { const caseOption = context.options[0] const options = context.options[1] || {} const caseType = allowedCaseOptions.indexOf(caseOption) !== -1 ? caseOption : defaultCase - const ignores = toRegExpList(options.ignores || []) + const ignores = (options.ignores || []).map(toRegExp) const registeredComponentsOnly = options.registeredComponentsOnly !== false const tokens = context.parserServices.getTemplateBodyTokenStore && context.parserServices.getTemplateBodyTokenStore() diff --git a/lib/utils/regexp.js b/lib/utils/regexp.js new file mode 100644 index 000000000..023654f02 --- /dev/null +++ b/lib/utils/regexp.js @@ -0,0 +1,38 @@ +const RE_REGEXP_CHAR = /[\\^$.*+?()[\]{}|]/gu +const RE_HAS_REGEXP_CHAR = new RegExp(RE_REGEXP_CHAR.source) + +const RE_REGEXP_STR = /^\/(.+)\/(.*)$/u + +/** + * Escapes the `RegExp` special characters "^", "$", "\", ".", "*", "+", + * "?", "(", ")", "[", "]", "{", "}", and "|" in `string`. + * + * @param {string} string The string to escape. + * @returns {string} Returns the escaped string. + */ +function escape (string) { + return (string && RE_HAS_REGEXP_CHAR.test(string)) + ? string.replace(RE_REGEXP_CHAR, '\\$&') + : string +} + +/** + * Convert a string to the `RegExp`. + * Normal strings (e.g. `"foo"`) is converted to `/^foo$/` of `RegExp`. + * Strings like `"/^foo/i"` are converted to `/^foo/i` of `RegExp`. + * + * @param {string} string The string to convert. + * @returns {string} Returns the `RegExp`. + */ +function toRegExp (string) { + const parts = RE_REGEXP_STR.exec(string) + if (parts) { + return new RegExp(parts[1], parts[2]) + } + return new RegExp(`^${escape(string)}$`) +} + +module.exports = { + escape, + toRegExp +} diff --git a/tests/lib/utils/regexp.js b/tests/lib/utils/regexp.js new file mode 100644 index 000000000..dc1fabec3 --- /dev/null +++ b/tests/lib/utils/regexp.js @@ -0,0 +1,40 @@ +'use strict' + +const { escape, toRegExp } = require('../../../lib/utils/regexp') +const chai = require('chai') + +const assert = chai.assert + +const ESCAPED = '\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\' +const UNESCAPED = '^$.*+?()[]{}|\\' + +describe('escape()', () => { + it('should escape values', () => { + assert.strictEqual(escape(UNESCAPED), ESCAPED) + assert.strictEqual(escape(UNESCAPED + UNESCAPED), ESCAPED + ESCAPED) + }) + + it('should handle strings with nothing to escape', () => { + assert.strictEqual(escape('abc'), 'abc') + }) + + it('should return an empty string for empty values', () => { + assert.strictEqual(escape(null), null) + assert.strictEqual(escape(undefined), undefined) + assert.strictEqual(escape(''), '') + }) +}) + +describe('toRegExp()', () => { + it('should be convert to RegExp', () => { + assert.deepEqual(toRegExp('foo'), /^foo$/) + assert.deepEqual(toRegExp(UNESCAPED), new RegExp(`^${ESCAPED}$`)) + }) + + it('RegExp like string should be convert to RegExp', () => { + assert.deepEqual(toRegExp('/^foo/i'), /^foo/i) + assert.deepEqual(toRegExp('/.*/ius'), /.*/ius) + assert.deepEqual(toRegExp(`${/^bar/i}`), /^bar/i) + assert.deepEqual(toRegExp(`${/[\sA-Z]+/u}`), /[\sA-Z]+/u) + }) +}) From f1bb1001c23c7b20d168ac1468554645e8586e21 Mon Sep 17 00:00:00 2001 From: yosuke ota Date: Thu, 3 Jan 2019 09:51:13 +0900 Subject: [PATCH 6/6] update for Node 6 --- tests/lib/utils/regexp.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/lib/utils/regexp.js b/tests/lib/utils/regexp.js index dc1fabec3..8e38a9687 100644 --- a/tests/lib/utils/regexp.js +++ b/tests/lib/utils/regexp.js @@ -33,7 +33,7 @@ describe('toRegExp()', () => { it('RegExp like string should be convert to RegExp', () => { assert.deepEqual(toRegExp('/^foo/i'), /^foo/i) - assert.deepEqual(toRegExp('/.*/ius'), /.*/ius) + assert.deepEqual(toRegExp('/.*/iu'), /.*/iu) assert.deepEqual(toRegExp(`${/^bar/i}`), /^bar/i) assert.deepEqual(toRegExp(`${/[\sA-Z]+/u}`), /[\sA-Z]+/u) })