From f38dd26342bdae032d686fcac46093939d4d8f62 Mon Sep 17 00:00:00 2001 From: Eduardo San Martin Morote Date: Thu, 18 Nov 2021 12:10:59 +0100 Subject: [PATCH 1/9] docs: update algolia credentials --- docs/.vuepress/config.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js index 5bcb7913b..68aec1af9 100644 --- a/docs/.vuepress/config.js +++ b/docs/.vuepress/config.js @@ -184,7 +184,8 @@ module.exports = { }, algolia: { - apiKey: 'b2b69365da747a9a9635cda391317c36', + appId: '2L4MGZSULB', + apiKey: 'fdf57932b27a6c230d01a890492ab76d', indexName: 'eslint-plugin-vue' } } From b08fe0b4a40691920e0152146703a48a3043181e Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Mon, 29 Nov 2021 17:34:49 +0800 Subject: [PATCH 2/9] docs: add missing backticks (#1730) --- docs/rules/component-definition-name-casing.md | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/docs/rules/component-definition-name-casing.md b/docs/rules/component-definition-name-casing.md index 5e89ace75..615b876f9 100644 --- a/docs/rules/component-definition-name-casing.md +++ b/docs/rules/component-definition-name-casing.md @@ -31,7 +31,7 @@ Default casing is set to `PascalCase`. - `"PascalCase"` (default) ... enforce component definition names to pascal case. - `"kebab-case"` ... enforce component definition names to kebab case. -### `"PascalCase" (default) +### `"PascalCase"` (default) @@ -64,18 +64,18 @@ export default { ```js /* ✓ GOOD */ Vue.component('MyComponent', { - + }) /* ✗ BAD */ Vue.component('my-component', { - + }) ``` -### `"kebab-case" +### `"kebab-case"` @@ -108,12 +108,12 @@ export default { ```js /* ✓ GOOD */ Vue.component('my-component', { - + }) /* ✗ BAD */ Vue.component('MyComponent', { - + }) ``` From 10dd1a99aa983754deb76383b224d48ebb2cbe91 Mon Sep 17 00:00:00 2001 From: Pig Fang Date: Tue, 30 Nov 2021 13:06:13 +0800 Subject: [PATCH 3/9] Add `vue/component-options-name-casing` rule (#1725) * Add `vue/component-options-name-casing` rule * fix docs * fix demo * refactor * fix auto-fix * fix checking kebab-case * provide suggestions * accept suggestions --- docs/rules/README.md | 1 + docs/rules/component-options-name-casing.md | 165 ++++++ lib/index.js | 1 + lib/rules/component-options-name-casing.js | 115 ++++ .../rules/component-options-name-casing.js | 558 ++++++++++++++++++ 5 files changed, 840 insertions(+) create mode 100644 docs/rules/component-options-name-casing.md create mode 100644 lib/rules/component-options-name-casing.js create mode 100644 tests/lib/rules/component-options-name-casing.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 5794cc3ed..2af1c06ca 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -311,6 +311,7 @@ For example: | [vue/block-tag-newline](./block-tag-newline.md) | enforce line breaks after opening and before closing block-level tags | :wrench: | | [vue/component-api-style](./component-api-style.md) | enforce component API style | | | [vue/component-name-in-template-casing](./component-name-in-template-casing.md) | enforce specific casing for the component naming style in template | :wrench: | +| [vue/component-options-name-casing](./component-options-name-casing.md) | enforce the casing of component name in `components` options | :wrench::bulb: | | [vue/custom-event-name-casing](./custom-event-name-casing.md) | enforce specific casing for custom event name | | | [vue/html-button-has-type](./html-button-has-type.md) | disallow usage of button without an explicit type attribute | | | [vue/html-comment-content-newline](./html-comment-content-newline.md) | enforce unified line brake in HTML comments | :wrench: | diff --git a/docs/rules/component-options-name-casing.md b/docs/rules/component-options-name-casing.md new file mode 100644 index 000000000..9a956553b --- /dev/null +++ b/docs/rules/component-options-name-casing.md @@ -0,0 +1,165 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/component-options-name-casing +description: enforce the casing of component name in `components` options +--- +# vue/component-options-name-casing + +> enforce the casing of component name in `components` options + +- :exclamation: ***This rule has not been released yet.*** +- :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. +- :bulb: Some problems reported by this rule are manually fixable by editor [suggestions](https://eslint.org/docs/developer-guide/working-with-rules#providing-suggestions). + +## :book: Rule Details + +This rule aims to enforce casing of the component names in `components` options. + +## :wrench: Options + +```json +{ + "vue/component-options-name-casing": ["error", "PascalCase" | "kebab-case" | "camelCase"] +} +``` + +This rule has an option which can be one of these values: + +- `"PascalCase"` (default) ... enforce component names to pascal case. +- `"kebab-case"` ... enforce component names to kebab case. +- `"camelCase"` ... enforce component names to camel case. + +Please note that if you use kebab case in `components` options, +you can **only** use kebab case in template; +and if you use camel case in `components` options, +you **can't** use pascal case in template. + +For demonstration, the code example is invalid: + +```vue + + + +``` + +### `"PascalCase"` (default) + + + +```vue + +``` + + + + + +```vue + +``` + + + +### `"kebab-case"` + + + +```vue + +``` + + + + + +```vue + +``` + + + +### `"camelCase"` + + + +```vue + +``` + + + + + +```vue + +``` + + + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/component-options-name-casing.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/component-options-name-casing.js) diff --git a/lib/index.js b/lib/index.js index c9969917c..0adbf3f74 100644 --- a/lib/index.js +++ b/lib/index.js @@ -24,6 +24,7 @@ module.exports = { 'component-api-style': require('./rules/component-api-style'), 'component-definition-name-casing': require('./rules/component-definition-name-casing'), 'component-name-in-template-casing': require('./rules/component-name-in-template-casing'), + 'component-options-name-casing': require('./rules/component-options-name-casing'), 'component-tags-order': require('./rules/component-tags-order'), 'custom-event-name-casing': require('./rules/custom-event-name-casing'), 'dot-location': require('./rules/dot-location'), diff --git a/lib/rules/component-options-name-casing.js b/lib/rules/component-options-name-casing.js new file mode 100644 index 000000000..ad70c1546 --- /dev/null +++ b/lib/rules/component-options-name-casing.js @@ -0,0 +1,115 @@ +/** + * @author Pig Fang + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const utils = require('../utils') +const casing = require('../utils/casing') + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * @param {import('../../typings/eslint-plugin-vue/util-types/ast').Expression} node + * @returns {string | null} + */ +function getOptionsComponentName(node) { + if (node.type === 'Identifier') { + return node.name + } + if (node.type === 'Literal') { + return typeof node.value === 'string' ? node.value : null + } + return null +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'enforce the casing of component name in `components` options', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/component-options-name-casing.html' + }, + fixable: 'code', + hasSuggestions: true, + schema: [{ enum: casing.allowedCaseOptions }], + messages: { + caseNotMatched: 'Component name "{{component}}" is not {{caseType}}.', + possibleRenaming: 'Rename component name to be in {{caseType}}.' + } + }, + /** @param {RuleContext} context */ + create(context) { + const caseType = context.options[0] || 'PascalCase' + + const canAutoFix = caseType === 'PascalCase' + const checkCase = casing.getChecker(caseType) + const convert = casing.getConverter(caseType) + + return utils.executeOnVue(context, (obj) => { + const node = utils.findProperty(obj, 'components') + if (!node || node.value.type !== 'ObjectExpression') { + return + } + + node.value.properties.forEach((property) => { + if (property.type !== 'Property') { + return + } + + const name = getOptionsComponentName(property.key) + if (!name || checkCase(name)) { + return + } + + context.report({ + node: property.key, + messageId: 'caseNotMatched', + data: { + component: name, + caseType + }, + fix: canAutoFix + ? (fixer) => { + const converted = convert(name) + return property.shorthand + ? fixer.replaceText(property, `${converted}: ${name}`) + : fixer.replaceText(property.key, converted) + } + : undefined, + suggest: canAutoFix + ? undefined + : [ + { + messageId: 'possibleRenaming', + data: { caseType }, + fix: (fixer) => { + const converted = convert(name) + if (caseType === 'kebab-case') { + return property.shorthand + ? fixer.replaceText(property, `'${converted}': ${name}`) + : fixer.replaceText(property.key, `'${converted}'`) + } + return property.shorthand + ? fixer.replaceText(property, `${converted}: ${name}`) + : fixer.replaceText(property.key, converted) + } + } + ] + }) + }) + }) + } +} diff --git a/tests/lib/rules/component-options-name-casing.js b/tests/lib/rules/component-options-name-casing.js new file mode 100644 index 000000000..504d98b0c --- /dev/null +++ b/tests/lib/rules/component-options-name-casing.js @@ -0,0 +1,558 @@ +/** + * @author Pig Fang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/component-options-name-casing') + +const tester = new RuleTester({ + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('component-options-name-casing', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + export default { + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + ...components + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar: fooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar + } + } + `, + options: ['PascalCase'] + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar + } + } + `, + options: ['camelCase'] + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar: FooBar + } + } + `, + options: ['camelCase'] + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + 'foo-bar': fooBar + } + } + `, + options: ['kebab-case'] + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + 'foo-bar': FooBar + } + } + `, + options: ['kebab-case'] + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar + } + } + `, + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'fooBar', + caseType: 'PascalCase' + }, + line: 4, + column: 13, + endColumn: 19 + } + ], + output: ` + export default { + components: { + FooBar: fooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar: FooBar + } + } + `, + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'fooBar', + caseType: 'PascalCase' + }, + line: 4, + column: 13, + endColumn: 19 + } + ], + output: ` + export default { + components: { + FooBar: FooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar + } + } + `, + options: ['PascalCase'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'fooBar', + caseType: 'PascalCase' + }, + line: 4, + column: 13, + endColumn: 19 + } + ], + output: ` + export default { + components: { + FooBar: fooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar: FooBar + } + } + `, + options: ['PascalCase'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'fooBar', + caseType: 'PascalCase' + }, + line: 4, + column: 13, + endColumn: 19 + } + ], + output: ` + export default { + components: { + FooBar: FooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + 'foo-bar': FooBar + } + } + `, + options: ['PascalCase'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'foo-bar', + caseType: 'PascalCase' + }, + line: 4, + column: 13, + endColumn: 22 + } + ], + output: ` + export default { + components: { + FooBar: FooBar + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar + } + } + `, + options: ['camelCase'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'FooBar', + caseType: 'camelCase' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'camelCase' }, + output: ` + export default { + components: { + fooBar: FooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar: fooBar + } + } + `, + options: ['camelCase'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'FooBar', + caseType: 'camelCase' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'camelCase' }, + output: ` + export default { + components: { + fooBar: fooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + 'foo-bar': fooBar + } + } + `, + options: ['camelCase'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'foo-bar', + caseType: 'camelCase' + }, + line: 4, + column: 13, + endColumn: 22, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'camelCase' }, + output: ` + export default { + components: { + fooBar: fooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar + } + } + `, + options: ['kebab-case'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'FooBar', + caseType: 'kebab-case' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'kebab-case' }, + output: ` + export default { + components: { + 'foo-bar': FooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar: fooBar + } + } + `, + options: ['kebab-case'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'FooBar', + caseType: 'kebab-case' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'kebab-case' }, + output: ` + export default { + components: { + 'foo-bar': fooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar + } + } + `, + options: ['kebab-case'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'fooBar', + caseType: 'kebab-case' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'kebab-case' }, + output: ` + export default { + components: { + 'foo-bar': fooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + fooBar: FooBar + } + } + `, + options: ['kebab-case'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'fooBar', + caseType: 'kebab-case' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'kebab-case' }, + output: ` + export default { + components: { + 'foo-bar': FooBar + } + } + ` + } + ] + } + ], + output: null + }, + { + filename: 'test.vue', + code: ` + export default { + components: { + FooBar, + 'my-component': MyComponent + } + } + `, + options: ['kebab-case'], + errors: [ + { + messageId: 'caseNotMatched', + data: { + component: 'FooBar', + caseType: 'kebab-case' + }, + line: 4, + column: 13, + endColumn: 19, + suggestions: [ + { + messageId: 'possibleRenaming', + data: { caseType: 'kebab-case' }, + output: ` + export default { + components: { + 'foo-bar': FooBar, + 'my-component': MyComponent + } + } + ` + } + ] + } + ], + output: null + } + ] +}) From 622ac268fc52ff5237a66db7c4d0cae1f9d6a035 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Fri, 3 Dec 2021 02:18:25 +0100 Subject: [PATCH 4/9] Allow `asyncData` in `vue/no-dupe-keys` (#1735) --- lib/rules/no-dupe-keys.js | 9 +------- tests/lib/rules/no-dupe-keys.js | 41 +++++++++++++++------------------ 2 files changed, 19 insertions(+), 31 deletions(-) diff --git a/lib/rules/no-dupe-keys.js b/lib/rules/no-dupe-keys.js index 61ffda40e..8b5e8e54c 100644 --- a/lib/rules/no-dupe-keys.js +++ b/lib/rules/no-dupe-keys.js @@ -14,14 +14,7 @@ const utils = require('../utils') // Rule Definition // ------------------------------------------------------------------------------ /** @type {GroupName[]} */ -const GROUP_NAMES = [ - 'props', - 'computed', - 'data', - 'asyncData', - 'methods', - 'setup' -] +const GROUP_NAMES = ['props', 'computed', 'data', 'methods', 'setup'] module.exports = { meta: { diff --git a/tests/lib/rules/no-dupe-keys.js b/tests/lib/rules/no-dupe-keys.js index 9e01f0856..2abae46d0 100644 --- a/tests/lib/rules/no-dupe-keys.js +++ b/tests/lib/rules/no-dupe-keys.js @@ -380,6 +380,24 @@ ruleTester.run('no-dupe-keys', rule, { } } ` + }, + { + // https://github.com/vuejs/eslint-plugin-vue/issues/1687 + filename: 'test.vue', + code: ` + export default { + asyncData() { + return { + foo: 1 + } + }, + data() { + return { + foo: 2 + } + }, + } + ` } ], @@ -709,29 +727,6 @@ ruleTester.run('no-dupe-keys', rule, { } ] }, - { - filename: 'test.vue', - code: ` - export default { - asyncData() { - return { - foo: 1 - } - }, - data() { - return { - foo: 2 - } - }, - } - `, - errors: [ - { - message: "Duplicated key 'foo'.", - line: 10 - } - ] - }, { filename: 'test.js', code: ` From fe82fb534b6ced51a54bb1c35a9c590f83a0f5b5 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 3 Dec 2021 11:02:24 +0900 Subject: [PATCH 5/9] Add support for ts4.5 to `vue/script-indent` rule (#1719) * Add support for ts4.5 to `vue/script-indent` rule * update deps --- .vscode/settings.json | 2 +- lib/utils/indent-common.js | 127 +++++++++++++----- lib/utils/indent-ts.js | 21 +++ package.json | 4 +- .../script-indent/ts-import-assertion-01.vue | 9 ++ .../script-indent/ts-import-assertion-02.vue | 15 +++ .../script-indent/ts-import-assertion-03.vue | 10 ++ .../script-indent/ts-import-assertion-04.vue | 10 ++ .../ts-type-only-import-export-01.vue | 17 +++ .../ts-type-only-import-export-02.vue | 23 ++++ .../ts-type-only-import-export-03.vue | 26 ++++ 11 files changed, 231 insertions(+), 33 deletions(-) create mode 100644 tests/fixtures/script-indent/ts-import-assertion-01.vue create mode 100644 tests/fixtures/script-indent/ts-import-assertion-02.vue create mode 100644 tests/fixtures/script-indent/ts-import-assertion-03.vue create mode 100644 tests/fixtures/script-indent/ts-import-assertion-04.vue create mode 100644 tests/fixtures/script-indent/ts-type-only-import-export-01.vue create mode 100644 tests/fixtures/script-indent/ts-type-only-import-export-02.vue create mode 100644 tests/fixtures/script-indent/ts-type-only-import-export-03.vue diff --git a/.vscode/settings.json b/.vscode/settings.json index 7bb646536..80e1f88fd 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -10,7 +10,7 @@ "json", "jsonc" ], - "typescript.tsdk": "node_modules/typescript/lib", + "typescript.tsdk": "./node_modules/typescript/lib", "vetur.validation.script": false, "[typescript]": { "editor.formatOnSave": true, diff --git a/lib/utils/indent-common.js b/lib/utils/indent-common.js index 39ef58670..d208d5a08 100644 --- a/lib/utils/indent-common.js +++ b/lib/utils/indent-common.js @@ -19,7 +19,8 @@ const { isNotOpeningBraceToken, isOpeningBracketToken, isClosingBracketToken, - isSemicolonToken + isSemicolonToken, + isNotSemicolonToken } = require('eslint-utils') const { isComment, @@ -1288,13 +1289,13 @@ module.exports.defineVisitor = function create( }, /** @param {ExportAllDeclaration} node */ ExportAllDeclaration(node) { - const tokens = tokenStore.getTokens(node) - const firstToken = /** @type {Token} */ (tokens.shift()) - if (isSemicolonToken(tokens[tokens.length - 1])) { - tokens.pop() - } + const exportToken = tokenStore.getFirstToken(node) + const tokens = [ + ...tokenStore.getTokensBetween(exportToken, node.source), + tokenStore.getFirstToken(node.source) + ] if (!node.exported) { - setOffset(tokens, 1, firstToken) + setOffset(tokens, 1, exportToken) } else { // export * as foo from "mod" const starToken = /** @type {Token} */ (tokens.find(isWildcard)) @@ -1302,10 +1303,28 @@ module.exports.defineVisitor = function create( const exportedToken = tokenStore.getTokenAfter(asToken) const afterTokens = tokens.slice(tokens.indexOf(exportedToken) + 1) - setOffset(starToken, 1, firstToken) + setOffset(starToken, 1, exportToken) setOffset(asToken, 1, starToken) setOffset(exportedToken, 1, starToken) - setOffset(afterTokens, 1, firstToken) + setOffset(afterTokens, 1, exportToken) + } + + // assertions + const lastToken = /** @type {Token} */ ( + tokenStore.getLastToken(node, isNotSemicolonToken) + ) + const assertionTokens = tokenStore.getTokensBetween( + node.source, + lastToken + ) + if (assertionTokens.length) { + const assertToken = /** @type {Token} */ (assertionTokens.shift()) + setOffset(assertToken, 0, exportToken) + const assertionOpen = assertionTokens.shift() + if (assertionOpen) { + setOffset(assertionOpen, 1, assertToken) + processNodeList(assertionTokens, assertionOpen, lastToken, 1) + } } }, /** @param {ExportDefaultDeclaration} node */ @@ -1328,28 +1347,66 @@ module.exports.defineVisitor = function create( const firstSpecifier = node.specifiers[0] if (!firstSpecifier || firstSpecifier.type === 'ExportSpecifier') { // export {foo, bar}; or export {foo, bar} from "mod"; - const leftParenToken = tokenStore.getFirstToken(node, 1) - const rightParenToken = /** @type {Token} */ ( - tokenStore.getLastToken(node, isClosingBraceToken) + const leftBraceTokens = firstSpecifier + ? tokenStore.getTokensBetween(exportToken, firstSpecifier) + : [tokenStore.getTokenAfter(exportToken)] + const rightBraceToken = /** @type {Token} */ ( + node.source + ? tokenStore.getTokenBefore(node.source, isClosingBraceToken) + : tokenStore.getLastToken(node, isClosingBraceToken) + ) + setOffset(leftBraceTokens, 0, exportToken) + processNodeList( + node.specifiers, + /** @type {Token} */ (last(leftBraceTokens)), + rightBraceToken, + 1 ) - setOffset(leftParenToken, 0, exportToken) - processNodeList(node.specifiers, leftParenToken, rightParenToken, 1) - - const maybeFromToken = tokenStore.getTokenAfter(rightParenToken) - if (maybeFromToken != null && maybeFromToken.value === 'from') { - const fromToken = maybeFromToken - const nameToken = tokenStore.getTokenAfter(fromToken) - setOffset([fromToken, nameToken], 1, exportToken) + + if (node.source) { + const tokens = tokenStore.getTokensBetween( + rightBraceToken, + node.source + ) + setOffset( + [...tokens, sourceCode.getFirstToken(node.source)], + 1, + exportToken + ) + + // assertions + const lastToken = /** @type {Token} */ ( + tokenStore.getLastToken(node, isNotSemicolonToken) + ) + const assertionTokens = tokenStore.getTokensBetween( + node.source, + lastToken + ) + if (assertionTokens.length) { + const assertToken = /** @type {Token} */ (assertionTokens.shift()) + setOffset(assertToken, 0, exportToken) + const assertionOpen = assertionTokens.shift() + if (assertionOpen) { + setOffset(assertionOpen, 1, assertToken) + processNodeList(assertionTokens, assertionOpen, lastToken, 1) + } + } } } else { // maybe babel parser } } }, - /** @param {ExportSpecifier} node */ - ExportSpecifier(node) { + /** @param {ExportSpecifier | ImportSpecifier} node */ + 'ExportSpecifier, ImportSpecifier'(node) { const tokens = tokenStore.getTokens(node) - const firstToken = /** @type {Token} */ (tokens.shift()) + let firstToken = /** @type {Token} */ (tokens.shift()) + if (firstToken.value === 'type') { + const typeToken = firstToken + firstToken = /** @type {Token} */ (tokens.shift()) + setOffset(firstToken, 0, typeToken) + } + setOffset(tokens, 1, firstToken) }, /** @param {ForInStatement | ForOfStatement} node */ @@ -1540,13 +1597,23 @@ module.exports.defineVisitor = function create( setOffset(fromToken, 1, importToken) setOffset(afterTokens, 0, fromToken) } - }, - /** @param {ImportSpecifier} node */ - ImportSpecifier(node) { - if (node.local.range[0] !== node.imported.range[0]) { - const tokens = tokenStore.getTokens(node) - const firstToken = /** @type {Token} */ (tokens.shift()) - setOffset(tokens, 1, firstToken) + + // assertions + const lastToken = /** @type {Token} */ ( + tokenStore.getLastToken(node, isNotSemicolonToken) + ) + const assertionTokens = tokenStore.getTokensBetween( + node.source, + lastToken + ) + if (assertionTokens.length) { + const assertToken = /** @type {Token} */ (assertionTokens.shift()) + setOffset(assertToken, 0, importToken) + const assertionOpen = assertionTokens.shift() + if (assertionOpen) { + setOffset(assertionOpen, 1, assertToken) + processNodeList(assertionTokens, assertionOpen, lastToken, 1) + } } }, /** @param {ImportNamespaceSpecifier} node */ diff --git a/lib/utils/indent-ts.js b/lib/utils/indent-ts.js index b98e9d105..496e892cd 100644 --- a/lib/utils/indent-ts.js +++ b/lib/utils/indent-ts.js @@ -1302,6 +1302,27 @@ function defineVisitor({ setOffset(atToken, 0, tokenStore.getFirstToken(decorators[0])) } }, + ImportAttribute(node) { + const firstToken = tokenStore.getFirstToken(node) + const keyTokens = getFirstAndLastTokens(node.key) + const prefixTokens = tokenStore.getTokensBetween( + firstToken, + keyTokens.firstToken + ) + setOffset(prefixTokens, 0, firstToken) + + setOffset(keyTokens.firstToken, 0, firstToken) + + const initToken = tokenStore.getFirstToken(node.value) + setOffset( + [ + ...tokenStore.getTokensBetween(keyTokens.lastToken, initToken), + initToken + ], + 1, + keyTokens.lastToken + ) + }, // ---------------------------------------------------------------------- // DEPRECATED NODES diff --git a/package.json b/package.json index 2c9bcbf18..96da9be83 100644 --- a/package.json +++ b/package.json @@ -65,7 +65,7 @@ "@types/natural-compare": "^1.4.0", "@types/node": "^13.13.5", "@types/semver": "^7.2.0", - "@typescript-eslint/parser": "^5.4.0", + "@typescript-eslint/parser": "^5.5.0", "@vuepress/plugin-pwa": "^1.4.1", "acorn": "^8.5.0", "env-cmd": "^10.1.0", @@ -82,7 +82,7 @@ "mocha": "^7.1.2", "nyc": "^15.1.0", "prettier": "^2.4.1", - "typescript": "^4.5.0-0", + "typescript": "^4.5.0", "vue-eslint-editor": "^1.1.0", "vuepress": "^1.8.2" } diff --git a/tests/fixtures/script-indent/ts-import-assertion-01.vue b/tests/fixtures/script-indent/ts-import-assertion-01.vue new file mode 100644 index 000000000..f14761841 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-assertion-01.vue @@ -0,0 +1,9 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-assertion-02.vue b/tests/fixtures/script-indent/ts-import-assertion-02.vue new file mode 100644 index 000000000..86fe80b4e --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-assertion-02.vue @@ -0,0 +1,15 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-assertion-03.vue b/tests/fixtures/script-indent/ts-import-assertion-03.vue new file mode 100644 index 000000000..ee88e92ce --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-assertion-03.vue @@ -0,0 +1,10 @@ + + diff --git a/tests/fixtures/script-indent/ts-import-assertion-04.vue b/tests/fixtures/script-indent/ts-import-assertion-04.vue new file mode 100644 index 000000000..76d2f2860 --- /dev/null +++ b/tests/fixtures/script-indent/ts-import-assertion-04.vue @@ -0,0 +1,10 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-only-import-export-01.vue b/tests/fixtures/script-indent/ts-type-only-import-export-01.vue new file mode 100644 index 000000000..97c6dc192 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-only-import-export-01.vue @@ -0,0 +1,17 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-only-import-export-02.vue b/tests/fixtures/script-indent/ts-type-only-import-export-02.vue new file mode 100644 index 000000000..0ef5f1106 --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-only-import-export-02.vue @@ -0,0 +1,23 @@ + + diff --git a/tests/fixtures/script-indent/ts-type-only-import-export-03.vue b/tests/fixtures/script-indent/ts-type-only-import-export-03.vue new file mode 100644 index 000000000..ca280f03c --- /dev/null +++ b/tests/fixtures/script-indent/ts-type-only-import-export-03.vue @@ -0,0 +1,26 @@ + + From 8a0b2c8142c4c413b84fcd31dc0e714a398bb30e Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Fri, 3 Dec 2021 12:07:44 +0100 Subject: [PATCH 6/9] Add new rule `vue/prefer-separate-static-class` (#1729) * Add new rule `vue/prefer-separate-static-class` * Also find static identifier object keys * Add auto-fix * Fix removing whole class directive if it's not empty * Simplify check with `property.computed` * Change rule type to `suggestion` * Make rule docs more consistent * Drop unnecessary `references` parameter --- docs/rules/README.md | 1 + docs/rules/prefer-separate-static-class.md | 43 +++ lib/index.js | 1 + lib/rules/prefer-separate-static-class.js | 231 ++++++++++++ .../lib/rules/prefer-separate-static-class.js | 333 ++++++++++++++++++ 5 files changed, 609 insertions(+) create mode 100644 docs/rules/prefer-separate-static-class.md create mode 100644 lib/rules/prefer-separate-static-class.js create mode 100644 tests/lib/rules/prefer-separate-static-class.js diff --git a/docs/rules/README.md b/docs/rules/README.md index 2af1c06ca..735dc87d0 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -351,6 +351,7 @@ For example: | [vue/no-useless-v-bind](./no-useless-v-bind.md) | disallow unnecessary `v-bind` directives | :wrench: | | [vue/no-v-text](./no-v-text.md) | disallow use of v-text | | | [vue/padding-line-between-blocks](./padding-line-between-blocks.md) | require or disallow padding lines between blocks | :wrench: | +| [vue/prefer-separate-static-class](./prefer-separate-static-class.md) | require static class names in template to be in a separate `class` attribute | :wrench: | | [vue/require-direct-export](./require-direct-export.md) | require the component to be directly exported | | | [vue/require-emit-validator](./require-emit-validator.md) | require type definitions in emits | :bulb: | | [vue/require-expose](./require-expose.md) | require declare public properties using `expose` | :bulb: | diff --git a/docs/rules/prefer-separate-static-class.md b/docs/rules/prefer-separate-static-class.md new file mode 100644 index 000000000..155c4fb5c --- /dev/null +++ b/docs/rules/prefer-separate-static-class.md @@ -0,0 +1,43 @@ +--- +pageClass: rule-details +sidebarDepth: 0 +title: vue/prefer-separate-static-class +description: require static class names in template to be in a separate `class` attribute +--- +# vue/prefer-separate-static-class + +> require static class names in template to be in a separate `class` attribute + +- :exclamation: ***This rule has not been released yet.*** +- :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. + +## :book: Rule Details + +This rule reports static class names in dynamic class attributes. + + + +```vue + +``` + + + +## :wrench: Options + +Nothing. + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/prefer-separate-static-class.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/prefer-separate-static-class.js) diff --git a/lib/index.js b/lib/index.js index 0adbf3f74..3c628c559 100644 --- a/lib/index.js +++ b/lib/index.js @@ -154,6 +154,7 @@ module.exports = { 'operator-linebreak': require('./rules/operator-linebreak'), 'order-in-components': require('./rules/order-in-components'), 'padding-line-between-blocks': require('./rules/padding-line-between-blocks'), + 'prefer-separate-static-class': require('./rules/prefer-separate-static-class'), 'prefer-template': require('./rules/prefer-template'), 'prop-name-casing': require('./rules/prop-name-casing'), 'require-component-is': require('./rules/require-component-is'), diff --git a/lib/rules/prefer-separate-static-class.js b/lib/rules/prefer-separate-static-class.js new file mode 100644 index 000000000..504d95d60 --- /dev/null +++ b/lib/rules/prefer-separate-static-class.js @@ -0,0 +1,231 @@ +/** + * @author Flo Edelmann + * See LICENSE file in root directory for full license. + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const { defineTemplateBodyVisitor, getStringLiteralValue } = require('../utils') + +// ------------------------------------------------------------------------------ +// Helpers +// ------------------------------------------------------------------------------ + +/** + * @param {ASTNode} node + * @returns {node is Literal | TemplateLiteral} + */ +function isStringLiteral(node) { + return ( + (node.type === 'Literal' && typeof node.value === 'string') || + (node.type === 'TemplateLiteral' && node.expressions.length === 0) + ) +} + +/** + * @param {Expression | VForExpression | VOnExpression | VSlotScopeExpression | VFilterSequenceExpression} expressionNode + * @returns {(Literal | TemplateLiteral | Identifier)[]} + */ +function findStaticClasses(expressionNode) { + if (isStringLiteral(expressionNode)) { + return [expressionNode] + } + + if (expressionNode.type === 'ArrayExpression') { + return expressionNode.elements.flatMap((element) => { + if (element === null || element.type === 'SpreadElement') { + return [] + } + return findStaticClasses(element) + }) + } + + if (expressionNode.type === 'ObjectExpression') { + return expressionNode.properties.flatMap((property) => { + if ( + property.type === 'Property' && + property.value.type === 'Literal' && + property.value.value === true && + (isStringLiteral(property.key) || + (property.key.type === 'Identifier' && !property.computed)) + ) { + return [property.key] + } + return [] + }) + } + + return [] +} + +/** + * @param {VAttribute | VDirective} attributeNode + * @returns {attributeNode is VAttribute & { value: VLiteral }} + */ +function isStaticClassAttribute(attributeNode) { + return ( + !attributeNode.directive && + attributeNode.key.name === 'class' && + attributeNode.value !== null + ) +} + +/** + * Removes the node together with the comma before or after the node. + * @param {RuleFixer} fixer + * @param {ParserServices.TokenStore} tokenStore + * @param {ASTNode} node + */ +function* removeNodeWithComma(fixer, tokenStore, node) { + const prevToken = tokenStore.getTokenBefore(node) + if (prevToken.type === 'Punctuator' && prevToken.value === ',') { + yield fixer.removeRange([prevToken.range[0], node.range[1]]) + return + } + + const [nextToken, nextNextToken] = tokenStore.getTokensAfter(node, { + count: 2 + }) + if ( + nextToken.type === 'Punctuator' && + nextToken.value === ',' && + (nextNextToken.type !== 'Punctuator' || + (nextNextToken.value !== ']' && nextNextToken.value !== '}')) + ) { + yield fixer.removeRange([node.range[0], nextNextToken.range[0]]) + return + } + + yield fixer.remove(node) +} + +// ------------------------------------------------------------------------------ +// Rule Definition +// ------------------------------------------------------------------------------ + +module.exports = { + meta: { + type: 'suggestion', + docs: { + description: + 'require static class names in template to be in a separate `class` attribute', + categories: undefined, + url: 'https://eslint.vuejs.org/rules/prefer-separate-static-class.html' + }, + fixable: 'code', + schema: [], + messages: { + preferSeparateStaticClass: + 'Static class "{{className}}" should be in a static `class` attribute.' + } + }, + /** @param {RuleContext} context */ + create(context) { + return defineTemplateBodyVisitor(context, { + /** @param {VDirectiveKey} directiveKeyNode */ + "VAttribute[directive=true] > VDirectiveKey[name.name='bind'][argument.name='class']"( + directiveKeyNode + ) { + const attributeNode = directiveKeyNode.parent + if (!attributeNode.value || !attributeNode.value.expression) { + return + } + + const expressionNode = attributeNode.value.expression + const staticClassNameNodes = findStaticClasses(expressionNode) + + for (const staticClassNameNode of staticClassNameNodes) { + const className = + staticClassNameNode.type === 'Identifier' + ? staticClassNameNode.name + : getStringLiteralValue(staticClassNameNode, true) + + if (className === null) { + continue + } + + context.report({ + node: staticClassNameNode, + messageId: 'preferSeparateStaticClass', + data: { className }, + *fix(fixer) { + let dynamicClassDirectiveRemoved = false + + yield* removeFromClassDirective() + yield* addToClassAttribute() + + /** + * Remove class from dynamic `:class` directive. + */ + function* removeFromClassDirective() { + if (isStringLiteral(expressionNode)) { + yield fixer.remove(attributeNode) + dynamicClassDirectiveRemoved = true + return + } + + const listElement = + staticClassNameNode.parent.type === 'Property' + ? staticClassNameNode.parent + : staticClassNameNode + + const listNode = listElement.parent + if ( + listNode.type === 'ArrayExpression' || + listNode.type === 'ObjectExpression' + ) { + const elements = + listNode.type === 'ObjectExpression' + ? listNode.properties + : listNode.elements + + if (elements.length === 1 && listNode === expressionNode) { + yield fixer.remove(attributeNode) + dynamicClassDirectiveRemoved = true + return + } + + const tokenStore = + context.parserServices.getTemplateBodyTokenStore() + + if (elements.length === 1) { + yield* removeNodeWithComma(fixer, tokenStore, listNode) + return + } + + yield* removeNodeWithComma(fixer, tokenStore, listElement) + } + } + + /** + * Add class to static `class` attribute. + */ + function* addToClassAttribute() { + const existingStaticClassAttribute = + attributeNode.parent.attributes.find(isStaticClassAttribute) + if (existingStaticClassAttribute) { + const literalNode = existingStaticClassAttribute.value + yield fixer.replaceText( + literalNode, + `"${literalNode.value} ${className}"` + ) + return + } + + // new static `class` attribute + const separator = dynamicClassDirectiveRemoved ? '' : ' ' + yield fixer.insertTextBefore( + attributeNode, + `class="${className}"${separator}` + ) + } + } + }) + } + } + }) + } +} diff --git a/tests/lib/rules/prefer-separate-static-class.js b/tests/lib/rules/prefer-separate-static-class.js new file mode 100644 index 000000000..498fc90dd --- /dev/null +++ b/tests/lib/rules/prefer-separate-static-class.js @@ -0,0 +1,333 @@ +/** + * @author Flo Edelmann + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/prefer-separate-static-class') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('prefer-separate-static-class', rule, { + valid: [ + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: `` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 30, + endColumn: 44 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 24, + endColumn: 38 + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 24, + endColumn: 38 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 24, + endColumn: 38 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 25, + endColumn: 39 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 25, + endColumn: 39 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "foo" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 25, + endColumn: 28 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 26, + endColumn: 40 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 25, + endColumn: 39 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 58, + endColumn: 72 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 25, + endColumn: 39 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 48, + endColumn: 62 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: + 'Static class "staticClass" should be in a static `class` attribute.', + line: 1, + endLine: 1, + column: 40, + endColumn: 51 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: + 'Static class "static-class" should be in a static `class` attribute.', + line: 7, + endLine: 7, + column: 40, + endColumn: 54 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: + 'Static class "static-class-a" should be in a static `class` attribute.', + line: 7, + endLine: 7, + column: 15, + endColumn: 31 + }, + { + message: + 'Static class "static-class-b" should be in a static `class` attribute.', + line: 8, + endLine: 8, + column: 16, + endColumn: 32 + } + ] + } + ] +}) From 09d7bedc52678e87fd68af0a0f894d77d7765fc3 Mon Sep 17 00:00:00 2001 From: Yosuke Ota Date: Fri, 3 Dec 2021 23:23:36 +0900 Subject: [PATCH 7/9] Fix false positives for import binding in `vue/valid-define-emits` and `vue/valid-define-props` rules (#1736) * Fix false positives for import binding in `vue/valid-define-emits` and `vue/valid-define-props` rules * Apply suggestions from code review Co-authored-by: Flo Edelmann * fix Co-authored-by: Flo Edelmann --- lib/rules/valid-define-emits.js | 1 + lib/rules/valid-define-props.js | 1 + tests/lib/rules/valid-define-emits.js | 10 ++++++++++ tests/lib/rules/valid-define-props.js | 10 ++++++++++ 4 files changed, 22 insertions(+) diff --git a/lib/rules/valid-define-emits.js b/lib/rules/valid-define-emits.js index 6f433a835..d7480d538 100644 --- a/lib/rules/valid-define-emits.js +++ b/lib/rules/valid-define-emits.js @@ -79,6 +79,7 @@ module.exports = { variable.defs.length && variable.defs.every( (def) => + def.type !== 'ImportBinding' && utils.inRange(scriptSetup.range, def.name) && !utils.inRange(defineEmits.range, def.name) ) diff --git a/lib/rules/valid-define-props.js b/lib/rules/valid-define-props.js index 3a084dd68..849437b1e 100644 --- a/lib/rules/valid-define-props.js +++ b/lib/rules/valid-define-props.js @@ -80,6 +80,7 @@ module.exports = { variable.defs.length && variable.defs.every( (def) => + def.type !== 'ImportBinding' && utils.inRange(scriptSetup.range, def.name) && !utils.inRange(defineProps.range, def.name) ) diff --git a/tests/lib/rules/valid-define-emits.js b/tests/lib/rules/valid-define-emits.js index 557101d67..dd0b1f88f 100644 --- a/tests/lib/rules/valid-define-emits.js +++ b/tests/lib/rules/valid-define-emits.js @@ -117,6 +117,16 @@ tester.run('valid-define-emits', rule, { }); ` + }, + { + filename: 'test.vue', + code: ` + ` } ], invalid: [ diff --git a/tests/lib/rules/valid-define-props.js b/tests/lib/rules/valid-define-props.js index 10894e069..2d866274b 100644 --- a/tests/lib/rules/valid-define-props.js +++ b/tests/lib/rules/valid-define-props.js @@ -120,6 +120,16 @@ tester.run('valid-define-props', rule, { }); ` + }, + { + filename: 'test.vue', + code: ` + ` } ], invalid: [ From 024c8d837a092ae7153f0eb74bdb2363f85b5270 Mon Sep 17 00:00:00 2001 From: Flo Edelmann Date: Sat, 4 Dec 2021 14:07:12 +0100 Subject: [PATCH 8/9] Add built-in component tests in `vue/component-name-in-template-casing` (#1737) --- .../component-name-in-template-casing.js | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/tests/lib/rules/component-name-in-template-casing.js b/tests/lib/rules/component-name-in-template-casing.js index baeb66433..4c4d9f5cb 100644 --- a/tests/lib/rules/component-name-in-template-casing.js +++ b/tests/lib/rules/component-name-in-template-casing.js @@ -69,6 +69,10 @@ tester.run('component-name-in-template-casing', rule, { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] }, + { + code: '', + options: ['PascalCase', { registeredComponentsOnly: false }] + }, { code: '', options: ['PascalCase', { registeredComponentsOnly: false }] @@ -151,6 +155,19 @@ tester.run('component-name-in-template-casing', rule, { { code: '