From c675a61fa6bc3c3fb9b98894af06a690827f15e4 Mon Sep 17 00:00:00 2001 From: Douglas Wade Date: Sat, 26 Mar 2022 21:20:28 -0700 Subject: [PATCH 01/14] Fix #1786: Add rule match-component-import-name --- docs/rules/match-component-import-name.md | 30 ++++ lib/index.js | 1 + lib/rules/match-component-import-name.js | 131 ++++++++++++++ lib/utils/index.js | 74 ++++++++ .../lib/rules/match-component-import-name.js | 166 ++++++++++++++++++ 5 files changed, 402 insertions(+) create mode 100644 docs/rules/match-component-import-name.md create mode 100644 lib/rules/match-component-import-name.js create mode 100644 tests/lib/rules/match-component-import-name.js diff --git a/docs/rules/match-component-import-name.md b/docs/rules/match-component-import-name.md new file mode 100644 index 000000000..1512f625b --- /dev/null +++ b/docs/rules/match-component-import-name.md @@ -0,0 +1,30 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/match-component-import-name +description: xxx +--- +# vue/match-component-import-name + +> xxx + +- :exclamation: ***This rule has not been released yet.*** + +## :book: Rule Details + +This rule .... + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + diff --git a/lib/index.js b/lib/index.js index 22fe9a182..d412a6bf1 100644 --- a/lib/index.js +++ b/lib/index.js @@ -47,6 +47,7 @@ module.exports = { 'key-spacing': require('./rules/key-spacing'), 'keyword-spacing': require('./rules/keyword-spacing'), 'match-component-file-name': require('./rules/match-component-file-name'), + 'match-component-import-name': require('./rules/match-component-import-name'), 'max-attributes-per-line': require('./rules/max-attributes-per-line'), 'max-len': require('./rules/max-len'), 'multi-word-component-names': require('./rules/multi-word-component-names'), diff --git a/lib/rules/match-component-import-name.js b/lib/rules/match-component-import-name.js new file mode 100644 index 000000000..642391ff8 --- /dev/null +++ b/lib/rules/match-component-import-name.js @@ -0,0 +1,131 @@ +/** + * @author Doug Wade + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') +const casing = require('../utils/casing') + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +// ... + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'problem', + docs: { + description: '', + categories: undefined, + url: '' + }, + fixable: null, + schema: [ + { + type: 'object', + properties: { + prefix: { + type: 'string' + }, + casing: { + type: 'string' + } + }, + additionalProperties: false + } + ], + messages: { + // ... + } + }, + /** @param {RuleContext} context */ + create(context) { + const options = context.options[0] || {} + + /** + * @param {ExportDefaultDeclaration} node + * @return {Array} + */ + function getComponents(node) { + if (node.declaration.type !== 'ObjectExpression') { + return [] + } + + const componentProperty = node.declaration.properties + .filter(utils.isProperty) + .filter((property) => { + return utils.getStaticPropertyName(property) === 'components' + })[0] + + if ( + !componentProperty || + componentProperty.value.type !== 'ObjectExpression' + ) { + return [] + } + + return componentProperty.value.properties.filter(utils.isProperty) + } + + /** @param {Property} property */ + function propertyStartsWithPrefix(property) { + if (!options.prefix) { + return true + } + + const name = utils.getStaticPropertyName(property) + return name ? name.startsWith(options.prefix) : false + } + + return utils.defineScriptVisitor(context, { + ExportDefaultDeclaration(node) { + const components = getComponents(node) + + if (!components) { + return + } + + components.forEach( + /** @param {Property} property */ + (property) => { + if (!propertyStartsWithPrefix(property)) { + context.report({ + node: property, + message: `component alias ${utils.getStaticPropertyName( + property + )} should have the prefix ${options.prefix}` + }) + } + + if (property.value.type !== 'Identifier') { + return + } + + const prefix = options.prefix || '' + const importedName = utils.getStaticPropertyName(property) || '' + const expectedName = + options.casing === 'kebab' + ? prefix + casing.kebabCase(property.value.name) + : prefix + casing.pascalCase(property.value.name) + if (importedName !== expectedName) { + context.report({ + node: property, + message: `component alias ${importedName} should match ${expectedName}` + }) + } + } + ) + } + }) + } +} diff --git a/lib/utils/index.js b/lib/utils/index.js index 69cd84a31..f8cdf9d23 100644 --- a/lib/utils/index.js +++ b/lib/utils/index.js @@ -1015,6 +1015,12 @@ module.exports = { * @returns {VElement | null} the element of ` + ` + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'camel' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'kebab' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'camel' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'kebab' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'camel', prefix: 'Prefix' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'kebab', prefix: 'prefix-' }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ prefix: 'Prefix' }] + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'component alias InvalidExport should match SomeRandomName', + line: 2, + column: 47 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'component alias invalid-export should match InvalidExport', + line: 2, + column: 47 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'kebab' }], + errors: [ + { + message: 'component alias InvalidImport should match invalid-import', + line: 2, + column: 47 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ casing: 'camel', prefix: 'Prefix' }], + errors: [ + { + message: + 'component alias invalid-import should have the prefix Prefix', + line: 2, + column: 47 + }, + { + message: + 'component alias invalid-import should match PrefixInvalidImport', + line: 2, + column: 47 + } + ] + } + ] +}) From 9c7084e5149938c5a8ac7c708f575f96aec7093c Mon Sep 17 00:00:00 2001 From: Douglas Wade Date: Sat, 26 Mar 2022 21:42:49 -0700 Subject: [PATCH 02/14] Write the documentation for match-component-import-name --- docs/rules/match-component-import-name.md | 81 ++++++++++++++++++++--- 1 file changed, 73 insertions(+), 8 deletions(-) diff --git a/docs/rules/match-component-import-name.md b/docs/rules/match-component-import-name.md index 1512f625b..9c7e8dcc0 100644 --- a/docs/rules/match-component-import-name.md +++ b/docs/rules/match-component-import-name.md @@ -4,27 +4,92 @@ sidebarDepth: 0 title: vue/match-component-import-name description: xxx --- + # vue/match-component-import-name -> xxx +> require the registered component name to match the imported component name + +This rule reports if a the name of a registered component does not match its imported name. -- :exclamation: ***This rule has not been released yet.*** +- :exclamation: **_This rule has not been released yet._** ## :book: Rule Details -This rule .... +This rule has some options. + +```json +{ + "vue/match-component-import-name": [ + "error", + { + "prefix": "prefix-", + "case": "kebab" + } + ] +} +``` + +By default, this rule will validate that the imported name is the same casing. + +Case can be one of: `"kebab"` (`casing-like-this`) or `"pascal"` (`CasingLikeThis`) - +An optional prefix can be provided that must be prepended to all imports. -```vue - + + +```javascript +/* ✓ GOOD */ +export default { components: { AppButton } } + +/* ✗ BAD */ +export default { components: { SomeOtherName: AppButton } } +export default { components: { 'app-button': AppButton } } +``` + + + + + +```javascript +/* ✓ GOOD */ +export default { components: { 'app-button': AppButton } } + +/* ✗ BAD */ +export default { components: { SomeOtherName: AppButton } } +export default { components: { AppButton } } +``` + + + + + +```javascript +/* ✓ GOOD */ +export default { components: { PrefixAppButton: AppButton } } + +/* ✗ BAD */ +export default { components: { SomeOtherName: AppButton } } +export default { components: { 'app-button': AppButton } } +export default { components: { 'prefix-app-button': PrefixAppButton } } ``` ## :wrench: Options -Nothing. +```json +{ + "vue/match-component-import-name": [ + "error", + { + "prefix": "prefix-", + "case": "kebab" + } + ] +} +``` +- `"prefix": ""` ... array of file extensions to be verified. Default is set to the empty string. +- `"case": "pascal"` ... one of "kebab" or "pascal", indicating the casing of the registered name. Default is set to `pascal`. From eb435ae77995fca57f41160a71aa066db37f7dca Mon Sep 17 00:00:00 2001 From: Douglas Wade Date: Tue, 29 Mar 2022 22:45:36 -0700 Subject: [PATCH 03/14] fix(1786): Respond to PR feedback --- docs/rules/README.md | 2 ++ docs/rules/match-component-import-name.md | 21 +++++++++----- lib/rules/match-component-import-name.js | 28 +++++++------------ .../lib/rules/match-component-import-name.js | 10 +++---- 4 files changed, 31 insertions(+), 30 deletions(-) diff --git a/docs/rules/README.md b/docs/rules/README.md index b4ebfefd7..9a57bdce1 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -12,6 +12,7 @@ sidebarDepth: 0 :bulb: Indicates that some problems reported by the rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). ::: + ## Base Rules (Enabling Correct ESLint Parsing) Enforce all the rules in this category, as well as all higher priority rules, with: @@ -317,6 +318,7 @@ For example: | [vue/html-comment-content-spacing](./html-comment-content-spacing.md) | enforce unified spacing in HTML comments | :wrench: | | [vue/html-comment-indent](./html-comment-indent.md) | enforce consistent indentation in HTML comments | :wrench: | | [vue/match-component-file-name](./match-component-file-name.md) | require component name property to match its file name | | +| [vue/match-component-import-name](./match-component-import-name.md) | require the registered component name to match the imported component name | | | [vue/new-line-between-multi-line-property](./new-line-between-multi-line-property.md) | enforce new lines between multi-line properties in Vue components | :wrench: | | [vue/next-tick-style](./next-tick-style.md) | enforce Promise or callback style in `nextTick` | :wrench: | | [vue/no-bare-strings-in-template](./no-bare-strings-in-template.md) | disallow the use of bare strings in `