diff --git a/.circleci/config.yml b/.circleci/config.yml index e695f422a..0ba3d33dc 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -5,7 +5,6 @@ workflows: - eslint-v6 - eslint-v7 - ts-eslint-v4 - - node-v12 - node-v14 - node-v16 - lint @@ -36,7 +35,7 @@ jobs: eslint-v6: docker: - - image: node:12 + - image: node:14 steps: - run: name: Versions @@ -88,10 +87,6 @@ jobs: - run: name: Test command: npm test - node-v12: - <<: *node-base - docker: - - image: node:12 node-v14: <<: *node-base docker: diff --git a/.eslintrc.js b/.eslintrc.js index 3229de98d..b73aa7c94 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -3,7 +3,7 @@ module.exports = { root: true, parserOptions: { - ecmaVersion: 2018 + ecmaVersion: 'latest' }, env: { es6: true, @@ -14,9 +14,10 @@ module.exports = { 'plugin:eslint-plugin/recommended', 'prettier', 'plugin:node-dependencies/recommended', - 'plugin:jsonc/recommended-with-jsonc' + 'plugin:jsonc/recommended-with-jsonc', + 'plugin:unicorn/recommended' ], - plugins: ['eslint-plugin', 'prettier'], + plugins: ['eslint-plugin', 'prettier', 'unicorn'], rules: { 'accessor-pairs': 2, camelcase: [2, { properties: 'never' }], @@ -117,14 +118,27 @@ module.exports = { 'prefer-arrow-callback': 'error', 'prefer-spread': 'error', - 'dot-notation': 'error' + 'dot-notation': 'error', + 'arrow-body-style': 'error', + + 'unicorn/consistent-function-scoping': [ + 'error', + { checkArrowFunctions: false } + ], + 'unicorn/filename-case': 'off', + 'unicorn/no-null': 'off', + 'unicorn/no-array-callback-reference': 'off', // doesn't work well with TypeScript's custom type guards + 'unicorn/no-useless-undefined': 'off', + 'unicorn/prefer-optional-catch-binding': 'off', // not supported by current ESLint parser version + 'unicorn/prefer-module': 'off', + 'unicorn/prevent-abbreviations': 'off' }, overrides: [ { files: ['./**/*.vue'], parser: require.resolve('vue-eslint-parser'), parserOptions: { - ecmaVersion: 2020, + ecmaVersion: 'latest', sourceType: 'module' } }, diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 15c7d4193..dedfe4110 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -25,7 +25,7 @@ jobs: name: Test strategy: matrix: - node: [16, 17] + node: [17, 18] os: [ubuntu-latest] runs-on: ${{ matrix.os }} diff --git a/.markdownlint.yml b/.markdownlint.yml index cb76e21d5..b300725cc 100644 --- a/.markdownlint.yml +++ b/.markdownlint.yml @@ -5,6 +5,8 @@ no-inline-html: - badge - eslint-code-block - sup + - rules-table + - span # enforce consistency code-block-style: diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue index 2c6abd2cc..f71e606a3 100644 --- a/docs/.vuepress/components/eslint-code-block.vue +++ b/docs/.vuepress/components/eslint-code-block.vue @@ -90,7 +90,7 @@ export default { rules: this.rules, parser: 'vue-eslint-parser', parserOptions: { - ecmaVersion: 2020, + ecmaVersion: 'latest', sourceType: 'module', ecmaFeatures: { jsx: true diff --git a/docs/.vuepress/components/rules-table.vue b/docs/.vuepress/components/rules-table.vue new file mode 100644 index 000000000..29d31c4c6 --- /dev/null +++ b/docs/.vuepress/components/rules-table.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js index dc7e30771..03082d606 100644 --- a/docs/.vuepress/enhanceApp.js +++ b/docs/.vuepress/enhanceApp.js @@ -8,21 +8,19 @@ export default ( // siteData, // site metadata } ) => { - if (typeof window !== 'undefined') { - if (typeof window.process === 'undefined') { - window.process = new Proxy( - { - env: {}, - cwd: () => undefined - }, - { - get(target, name) { - // For debug - // console.log(name) - return target[name] - } + if (typeof window !== 'undefined' && typeof window.process === 'undefined') { + window.process = new Proxy( + { + env: {}, + cwd: () => undefined + }, + { + get(target, name) { + // For debug + // console.log(name) + return target[name] } - ) - } + } + ) } } diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl index 48680bbf3..724c68247 100644 --- a/docs/.vuepress/styles/index.styl +++ b/docs/.vuepress/styles/index.styl @@ -18,3 +18,9 @@ } } } + +.theme-container.rule-list .theme-default-content + max-width: 1280px; + +.emoji + font-family: "Segoe UI Emoji", "Segoe UI Symbol", "Apple Color Emoji", "Noto Color Emoji", "Noto Emoji", sans-serif diff --git a/docs/rules/README.md b/docs/rules/README.md index 26ce0e59d..b72928647 100644 --- a/docs/rules/README.md +++ b/docs/rules/README.md @@ -1,5 +1,6 @@ --- sidebarDepth: 0 +pageClass: rule-list --- # Available rules @@ -12,282 +13,179 @@ 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: - -```json -{ - "extends": "plugin:vue/base" -} -``` +Mark indicating rule type: -| Rule ID | Description | | -|:--------|:------------|:---| -| [vue/comment-directive](./comment-directive.md) | support comment-directives in ` diff --git a/docs/rules/no-restricted-custom-event.md b/docs/rules/no-restricted-custom-event.md index bc48def0a..b77feac14 100644 --- a/docs/rules/no-restricted-custom-event.md +++ b/docs/rules/no-restricted-custom-event.md @@ -31,7 +31,7 @@ This rule takes a list of strings, where each string is a custom event name or p +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + + + +```vue + +``` + + + +## :rocket: Version + +This rule was introduced in eslint-plugin-vue v9.0.0 + +## :mag: Implementation + +- [Rule source](https://github.com/vuejs/eslint-plugin-vue/blob/master/lib/rules/valid-model-definition.js) +- [Test source](https://github.com/vuejs/eslint-plugin-vue/blob/master/tests/lib/rules/valid-model-definition.js) diff --git a/docs/user-guide/README.md b/docs/user-guide/README.md index b083a6bd3..8129cad21 100644 --- a/docs/user-guide/README.md +++ b/docs/user-guide/README.md @@ -23,7 +23,7 @@ yarn add -D eslint eslint-plugin-vue ::: tip Requirements - ESLint v6.2.0 and above -- Node.js v12.22.x, v14.17.x, v16.x and above +- Node.js v14.17.x, v16.x and above ::: @@ -342,36 +342,15 @@ See also: "[Visual Studio Code](#editor-integrations)" section and [Vetur - Lint #### The variables used in the ` ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -144,9 +137,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -157,9 +148,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -173,9 +162,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -191,9 +178,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -205,9 +190,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -222,9 +205,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -240,9 +221,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -260,9 +239,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -276,9 +253,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -296,9 +271,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-duplicate-attributes') @@ -311,9 +284,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -324,9 +295,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -339,9 +308,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -353,9 +320,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -370,9 +335,7 @@ describe('comment-directive', () => {
Hello
` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -387,9 +350,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) assert.deepEqual(messages[0].ruleId, 'vue/no-parsing-error') @@ -417,6 +378,12 @@ describe('comment-directive', () => { }, useEslintrc: false }) + + async function lintMessages(code) { + const result = await eslint.lintText(code, { filePath: 'test.vue' }) + return result[0].messages + } + it('report unused ', async () => { const code = ` ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/comment-directive') @@ -447,9 +412,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -464,9 +427,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 1) assert.deepEqual(messages[0].ruleId, 'vue/comment-directive') @@ -486,9 +447,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 2) @@ -518,9 +477,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 4) @@ -554,9 +511,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) @@ -570,9 +525,7 @@ describe('comment-directive', () => { ` - const messages = ( - await eslint.lintText(code, { filePath: 'test.vue' }) - )[0].messages + const messages = await lintMessages(code) assert.deepEqual(messages.length, 0) }) diff --git a/tests/lib/rules/custom-event-name-casing.js b/tests/lib/rules/custom-event-name-casing.js index 00b76e4ed..8ad20377e 100644 --- a/tests/lib/rules/custom-event-name-casing.js +++ b/tests/lib/rules/custom-event-name-casing.js @@ -41,7 +41,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -67,7 +68,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -92,7 +94,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -117,7 +120,8 @@ tester.run('custom-event-name-casing', rule, { } } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -149,7 +153,8 @@ tester.run('custom-event-name-casing', rule, { }, } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -165,7 +170,8 @@ tester.run('custom-event-name-casing', rule, { }, } - ` + `, + options: ['kebab-case'] }, { filename: 'test.vue', @@ -269,6 +275,60 @@ tester.run('custom-event-name-casing', rule, { `, options: ['camelCase'] + }, + // Default + { + filename: 'test.vue', + code: ` + + + ` + }, + + // kebab-case + { + filename: 'test.vue', + code: ` + + + `, + options: ['kebab-case'] } ], invalid: [ @@ -296,6 +356,7 @@ tester.run('custom-event-name-casing', rule, { } `, + options: ['kebab-case'], errors: [ { message: "Custom event name 'fooBar' must be kebab-case.", @@ -344,6 +405,7 @@ tester.run('custom-event-name-casing', rule, { } `, + options: ['kebab-case'], errors: [ "Custom event name 'fooBar' must be kebab-case.", "Custom event name 'barBaz' must be kebab-case.", @@ -374,6 +436,7 @@ tester.run('custom-event-name-casing', rule, { } `, + options: ['kebab-case'], errors: [ "Custom event name 'fooBar' must be kebab-case.", "Custom event name 'barBaz' must be kebab-case.", @@ -448,6 +511,7 @@ tester.run('custom-event-name-casing', rule, { "Custom event name 'click/row' must be kebab-case." ] }, + // camelCase { filename: 'test.vue', code: ` @@ -479,6 +543,69 @@ tester.run('custom-event-name-casing', rule, { "Custom event name 'baz-qux' must be camelCase." ] }, + // Default + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + "Custom event name 'foo-bar' must be camelCase.", + "Custom event name 'bar-baz' must be camelCase.", + "Custom event name 'baz-qux' must be camelCase." + ] + }, + // kebab-case + { + filename: 'test.vue', + code: ` + + + `, + options: ['kebab-case'], + errors: [ + "Custom event name 'fooBar' must be kebab-case.", + "Custom event name 'barBaz' must be kebab-case.", + "Custom event name 'bazQux' must be kebab-case." + ] + }, { filename: 'test.vue', code: ` @@ -490,8 +617,8 @@ tester.run('custom-event-name-casing', rule, { `, errors: [ { - message: "Custom event name 'fooBar' must be kebab-case.", - line: 4 + message: "Custom event name 'foo-bar' must be camelCase.", + line: 5 } ] } diff --git a/tests/lib/rules/define-macros-order.js b/tests/lib/rules/define-macros-order.js index ce5f30040..90e6748d6 100644 --- a/tests/lib/rules/define-macros-order.js +++ b/tests/lib/rules/define-macros-order.js @@ -155,9 +155,7 @@ tester.run('define-macros-order', rule, { defineProps({ test: Boolean }) - defineEmits(['update:test']) - console.log('test1') console.log('test2') console.log('test3') @@ -192,7 +190,6 @@ tester.run('define-macros-order', rule, { defineProps({ test: Boolean }) - defineEmits(['update:test']) console.log('test1') @@ -261,7 +258,6 @@ tester.run('define-macros-order', rule, { } const emit = defineEmits<{(e: 'update:test'): void}>() - const props = withDefaults(defineProps(), { msg: 'hello', labels: () => ['one', 'two'] @@ -377,8 +373,7 @@ tester.run('define-macros-order', rule, { `, output: ` + defineEmits(['update:test']);const props = defineProps({ test: Boolean }); `, options: optionsEmitsFirst, errors: [ @@ -387,6 +382,56 @@ tester.run('define-macros-order', rule, { line: 3 } ] + }, + { + filename: 'test.vue', + code: ` + + + + `, + output: ` + + + + `, + errors: [ + { + message: message('defineProps'), + line: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: message('defineProps'), + line: 2 + } + ] } ] }) diff --git a/tests/lib/rules/experimental-script-setup-vars.js b/tests/lib/rules/experimental-script-setup-vars.js deleted file mode 100644 index 089019c7c..000000000 --- a/tests/lib/rules/experimental-script-setup-vars.js +++ /dev/null @@ -1,58 +0,0 @@ -/** - * @author Yosuke Ota - */ -'use strict' - -const { RuleTester } = require('eslint') -const rule = require('../../../lib/rules/experimental-script-setup-vars') - -const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020, sourceType: 'module' } -}) - -tester.run('experimental-script-setup-vars', rule, { - valid: [ - ` - `, - ` - `, - ` - `, - ` - ` - ], - invalid: [ - { - code: ` - - `, - errors: [ - { - message: 'Parsing error.', - line: 2 - } - ] - } - ] -}) diff --git a/tests/lib/rules/html-closing-bracket-newline.js b/tests/lib/rules/html-closing-bracket-newline.js index 4c07c80db..b72c46b97 100644 --- a/tests/lib/rules/html-closing-bracket-newline.js +++ b/tests/lib/rules/html-closing-bracket-newline.js @@ -337,6 +337,156 @@ tester.run('html-closing-bracket-newline', rule, { 'Expected 1 line break before closing bracket, but no line breaks found.', 'Expected 1 line break before closing bracket, but no line breaks found.' ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + singleline: 'never', + multiline: 'never' + } + ], + errors: [ + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 2, + column: 18, + endLine: 3, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 4, + column: 19, + endLine: 5, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 6, + column: 16, + endLine: 7, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 8, + column: 17, + endLine: 9, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 10, + column: 15, + endLine: 11, + endColumn: 9 + }, + { + message: + 'Expected no line breaks before closing bracket, but 1 line break found.', + line: 11, + column: 17, + endLine: 12, + endColumn: 9 + } + ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + singleline: 'always', + multiline: 'always' + } + ], + errors: [ + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 2, + column: 18, + endColumn: 18 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 3, + column: 19, + endColumn: 19 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 4, + column: 16, + endColumn: 16 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 5, + column: 17, + endColumn: 17 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 6, + column: 15, + endColumn: 15 + }, + { + message: + 'Expected 1 line break before closing bracket, but no line breaks found.', + line: 6, + column: 23, + endColumn: 23 + } + ] } ] }) diff --git a/tests/lib/rules/html-closing-bracket-spacing.js b/tests/lib/rules/html-closing-bracket-spacing.js index 154ba87b2..af03d0bfd 100644 --- a/tests/lib/rules/html-closing-bracket-spacing.js +++ b/tests/lib/rules/html-closing-bracket-spacing.js @@ -114,6 +114,56 @@ ruleTester.run('html-closing-bracket-spacing', rule, { } ] }, + { + code: ` + + + + `, + output: ` + + + + `, + errors: [ + { + message: "Expected no space before '>', but found.", + line: 2, + column: 18, + endColumn: 20 + }, + { + message: "Expected no space before '>', but found.", + line: 2, + column: 30, + endColumn: 32 + }, + { + message: "Expected no space before '>', but found.", + line: 3, + column: 16, + endColumn: 18 + }, + { + message: "Expected no space before '>', but found.", + line: 3, + column: 26, + endColumn: 28 + }, + { + message: "Expected no space before '>', but found.", + line: 4, + column: 15, + endColumn: 17 + }, + { + message: "Expected no space before '>', but found.", + line: 4, + column: 24, + endColumn: 26 + } + ] + }, { code: '', output: '', @@ -144,6 +194,63 @@ ruleTester.run('html-closing-bracket-spacing', rule, { endColumn: 10 } ] + }, + { + code: ` + + + + `, + output: ` + + + + `, + options: [ + { + startTag: 'always', + endTag: 'always', + selfClosingTag: 'never' + } + ], + errors: [ + { + message: "Expected a space before '>', but not found.", + line: 2, + column: 18, + endColumn: 19 + }, + { + message: "Expected a space before '>', but not found.", + line: 2, + column: 29, + endColumn: 30 + }, + { + message: "Expected a space before '>', but not found.", + line: 3, + column: 16, + endColumn: 17 + }, + { + message: "Expected a space before '>', but not found.", + line: 3, + column: 25, + endColumn: 26 + }, + { + message: "Expected a space before '>', but not found.", + line: 4, + column: 15, + endColumn: 16 + }, + { + message: "Expected a space before '>', but not found.", + line: 4, + column: 23, + endColumn: 24 + } + ] } ] }) diff --git a/tests/lib/rules/html-indent.js b/tests/lib/rules/html-indent.js index 8340f9423..1c817f1a3 100644 --- a/tests/lib/rules/html-indent.js +++ b/tests/lib/rules/html-indent.js @@ -58,10 +58,10 @@ function loadPatterns(additionalValid, additionalInvalid) { const lines = output.split('\n').map((text, number) => ({ number, text, - indentSize: (/^[ \t]+/.exec(text) || [''])[0].length + indentSize: (/^[\t ]+/.exec(text) || [''])[0].length })) const code = lines - .map((line) => line.text.replace(/^[ \t]+/, '')) + .map((line) => line.text.replace(/^[\t ]+/, '')) .join('\n') const errors = lines .map((line) => @@ -82,8 +82,8 @@ function loadPatterns(additionalValid, additionalInvalid) { .filter(Boolean) return { - valid: valid.concat(additionalValid), - invalid: invalid.concat(additionalInvalid) + valid: [...valid, ...additionalValid], + invalid: [...invalid, ...additionalInvalid] } } diff --git a/tests/lib/rules/match-component-file-name.js b/tests/lib/rules/match-component-file-name.js index d1b4c3371..83610962c 100644 --- a/tests/lib/rules/match-component-file-name.js +++ b/tests/lib/rules/match-component-file-name.js @@ -564,7 +564,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: 'MyComponent', + render() { return
} + } + ` + } + ] } ] }, @@ -581,7 +592,46 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: 'MyComponent', + render() { return
} + } + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.jsx', + code: ` + export default { + name: "MComponent", + render() { return
} + } + `, + options: [{ extensions: ['jsx'] }], + parserOptions: jsxParserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: "MyComponent", + render() { return
} + } + ` + } + ] } ] }, @@ -598,7 +648,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: \`MyComponent\`, + render() { return
} + } + ` + } + ] } ] }, @@ -620,7 +681,53 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.vue', + code: ` + + `, + options: [{ extensions: ['vue'] }], + parser: require.resolve('vue-eslint-parser'), + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + + ` + } + ] } ] }, @@ -640,7 +747,20 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + + ` + } + ] } ] }, @@ -659,7 +779,46 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + new Vue({ + name: 'MyComponent', + template: '
' + }) + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.js', + code: ` + new Vue({ + name: "MComponent", + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + new Vue({ + name: "MyComponent", + template: '
' + }) + ` + } + ] } ] }, @@ -676,7 +835,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + new Vue({ + name: \`MyComponent\`, + template: '
' + }) + ` + } + ] } ] }, @@ -692,7 +862,43 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.mixin({ + name: 'MyComponent', + }) + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.mixin({ + name: "MComponent", + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.mixin({ + name: "MyComponent", + }) + ` + } + ] } ] }, @@ -708,7 +914,17 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.mixin({ + name: \`MyComponent\`, + }) + ` + } + ] } ] }, @@ -724,7 +940,43 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.component('MyComponent', { + template: '
' + }) + ` + } + ] + } + ] + }, + { + filename: 'MyComponent.js', + code: ` + Vue.component("MComponent", { + template: '
' + }) + `, + options: [{ extensions: ['js'] }], + parserOptions, + errors: [ + { + message: + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.component("MyComponent", { + template: '
' + }) + ` + } + ] } ] }, @@ -740,7 +992,17 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + Vue.component(\`MyComponent\`, { + template: '
' + }) + ` + } + ] } ] }, @@ -756,7 +1018,17 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MComponent` should match file name `MyComponent`.' + 'Component name `MComponent` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + app.component(\`MyComponent\`, { + template: '
' + }) + ` + } + ] } ] }, @@ -775,7 +1047,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `my-component` should match file name `MyComponent`.' + 'Component name `my-component` should match file name `MyComponent`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: 'MyComponent', + render() { return
} + } + ` + } + ] } ] }, @@ -792,7 +1075,18 @@ ruleTester.run('match-component-file-name', rule, { errors: [ { message: - 'Component name `MyComponent` should match file name `my-component`.' + 'Component name `MyComponent` should match file name `my-component`.', + suggestions: [ + { + desc: 'Rename component to match file name.', + output: ` + export default { + name: 'my-component', + render() { return
} + } + ` + } + ] } ] } diff --git a/tests/lib/rules/name-property-casing.js b/tests/lib/rules/name-property-casing.js deleted file mode 100644 index a7a8d41cc..000000000 --- a/tests/lib/rules/name-property-casing.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * @fileoverview Define a style for the name property casing for consistency purposes - * @author Armano - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/name-property-casing') -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const parserOptions = { - ecmaVersion: 2018, - sourceType: 'module' -} - -const ruleTester = new RuleTester() -ruleTester.run('name-property-casing', rule, { - valid: [ - { - filename: 'test.vue', - code: ` - export default { - } - `, - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - ...name - } - `, - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'FooBar' - } - `, - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'FooBar' - } - `, - options: ['PascalCase'], - parserOptions - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo-bar' - } - `, - options: ['kebab-case'], - parserOptions - } - ], - - invalid: [ - { - filename: 'test.vue', - code: ` - export default { - name: 'foo-bar' - } - `, - output: ` - export default { - name: 'FooBar' - } - `, - parserOptions, - errors: [ - { - message: 'Property name "foo-bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo bar' - } - `, - output: null, - parserOptions, - errors: [ - { - message: 'Property name "foo bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo!bar' - } - `, - output: null, - parserOptions, - errors: [ - { - message: 'Property name "foo!bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.js', - code: ` - new Vue({ - name: 'foo!bar' - }) - `, - output: null, - parserOptions: { ecmaVersion: 6 }, - errors: [ - { - message: 'Property name "foo!bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo_bar' - } - `, - output: ` - export default { - name: 'FooBar' - } - `, - parserOptions, - errors: [ - { - message: 'Property name "foo_bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo_bar' - } - `, - output: ` - export default { - name: 'FooBar' - } - `, - options: ['PascalCase'], - parserOptions, - errors: [ - { - message: 'Property name "foo_bar" is not PascalCase.', - type: 'Literal', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - export default { - name: 'foo_bar' - } - `, - output: ` - export default { - name: 'foo-bar' - } - `, - options: ['kebab-case'], - parserOptions, - errors: [ - { - message: 'Property name "foo_bar" is not kebab-case.', - type: 'Literal', - line: 3 - } - ] - } - ] -}) diff --git a/tests/lib/rules/no-confusing-v-for-v-if.js b/tests/lib/rules/no-confusing-v-for-v-if.js deleted file mode 100644 index 47959aeb4..000000000 --- a/tests/lib/rules/no-confusing-v-for-v-if.js +++ /dev/null @@ -1,59 +0,0 @@ -/** - * @author Toru Nagashima - * @copyright 2017 Toru Nagashima. All rights reserved. - * See LICENSE file in root directory for full license. - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester -const rule = require('../../../lib/rules/no-confusing-v-for-v-if') - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } -}) - -tester.run('no-confusing-v-for-v-if', rule, { - valid: [ - { - 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: '', - errors: ["This 'v-if' should be moved to the wrapper element."] - }, - { - filename: 'test.vue', - code: '', - errors: ["This 'v-if' should be moved to the wrapper element."] - } - ] -}) diff --git a/tests/lib/rules/no-expose-after-await.js b/tests/lib/rules/no-expose-after-await.js index 2f33c58e6..d335da64a 100644 --- a/tests/lib/rules/no-expose-after-await.js +++ b/tests/lib/rules/no-expose-after-await.js @@ -101,7 +101,19 @@ tester.run('no-expose-after-await', rule, { + `, + parserOptions: { ecmaVersion: 2022 } + }, + { + filename: 'test.vue', + code: ` + `, parserOptions: { ecmaVersion: 2022 } @@ -122,7 +134,7 @@ tester.run('no-expose-after-await', rule, { `, errors: [ { - message: 'The `expose` after `await` expression are forbidden.', + message: '`expose` is forbidden after an `await` expression.', line: 6, column: 11 } @@ -142,11 +154,28 @@ tester.run('no-expose-after-await', rule, { `, errors: [ { - message: 'The `expose` after `await` expression are forbidden.', + message: '`expose` is forbidden after an `await` expression.', line: 6, column: 11 } ] + }, + { + filename: 'test.vue', + code: ` + + `, + parserOptions: { ecmaVersion: 2022 }, + errors: [ + { + message: '`defineExpose` is forbidden after an `await` expression.', + line: 4, + column: 7 + } + ] } ] }) diff --git a/tests/lib/rules/no-irregular-whitespace.js b/tests/lib/rules/no-irregular-whitespace.js index 9c3a40cdc..52c6dd0e4 100644 --- a/tests/lib/rules/no-irregular-whitespace.js +++ b/tests/lib/rules/no-irregular-whitespace.js @@ -11,17 +11,37 @@ const tester = new RuleTester({ parserOptions: { ecmaVersion: 2018 } }) -const IRREGULAR_WHITESPACES = - '\f\v\u0085\ufeff\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000'.split( - '' - ) -const IRREGULAR_LINE_TERMINATORS = '\u2028\u2029'.split('') -const ALL_IRREGULAR_WHITESPACES = [].concat( - IRREGULAR_WHITESPACES, - IRREGULAR_LINE_TERMINATORS -) +const IRREGULAR_WHITESPACES = [ + '\f', + '\v', + '\u0085', + '\uFEFF', + '\u00A0', + '\u1680', + '\u180E', + '\u2000', + '\u2001', + '\u2002', + '\u2003', + '\u2004', + '\u2005', + '\u2006', + '\u2007', + '\u2008', + '\u2009', + '\u200A', + '\u200B', + '\u202F', + '\u205F', + '\u3000' +] +const IRREGULAR_LINE_TERMINATORS = ['\u2028', '\u2029'] +const ALL_IRREGULAR_WHITESPACES = [ + ...IRREGULAR_WHITESPACES, + ...IRREGULAR_LINE_TERMINATORS +] const ALL_IRREGULAR_WHITESPACE_CODES = ALL_IRREGULAR_WHITESPACES.map((s) => - `000${s.charCodeAt(0).toString(16)}`.slice(-4) + `000${s.codePointAt(0).toString(16)}`.slice(-4) ) tester.run('no-irregular-whitespace', rule, { diff --git a/tests/lib/rules/no-lifecycle-after-await.js b/tests/lib/rules/no-lifecycle-after-await.js index bb7414682..e75995a5f 100644 --- a/tests/lib/rules/no-lifecycle-after-await.js +++ b/tests/lib/rules/no-lifecycle-after-await.js @@ -174,8 +174,7 @@ tester.run('no-lifecycle-after-await', rule, { `, errors: [ { - message: - 'The lifecycle hooks after `await` expression are forbidden.', + message: 'Lifecycle hooks are forbidden after an `await` expression.', line: 8, column: 11, endLine: 8, diff --git a/tests/lib/rules/no-reserved-component-names.js b/tests/lib/rules/no-reserved-component-names.js index cdb3ab7a2..4b39c0858 100644 --- a/tests/lib/rules/no-reserved-component-names.js +++ b/tests/lib/rules/no-reserved-component-names.js @@ -14,9 +14,7 @@ const RuleTester = require('eslint').RuleTester const htmlElements = require('../../../lib/utils/html-elements.json') const RESERVED_NAMES_IN_HTML = new Set([ ...htmlElements, - ...htmlElements.map( - (word) => word[0].toUpperCase() + word.substring(1, word.length) - ) + ...htmlElements.map((word) => word[0].toUpperCase() + word.slice(1)) ]) // ------------------------------------------------------------------------------ @@ -538,212 +536,188 @@ ruleTester.run('no-reserved-component-names', rule, { code: `fn1(component.data)`, parserOptions }, - ...vue2BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + ...vue2BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions - } - }), - ...vue3BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions + })), + ...vue3BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions - } - }), - ...vue3BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions + })), + ...vue3BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVueBuiltInComponents: true }] - } - }) + parserOptions, + options: [{ disallowVueBuiltInComponents: true }] + })) ], invalid: [ - ...invalidElements.map((name) => { - return { - filename: `${name}.vue`, - code: ` + ...invalidElements.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `Vue.component('${name}', component)`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Literal', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `app.component('${name}', component)`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Literal', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `Vue.component(\`${name}\`, {})`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'TemplateLiteral', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `app.component(\`${name}\`, {})`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'TemplateLiteral', - line: 1 - } - ] - } - }), - ...invalidElements.map((name) => { - return { - filename: 'test.vue', - code: `export default { + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `Vue.component('${name}', component)`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Literal', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `app.component('${name}', component)`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Literal', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `Vue.component(\`${name}\`, {})`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'TemplateLiteral', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `app.component(\`${name}\`, {})`, + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'TemplateLiteral', + line: 1 + } + ] + })), + ...invalidElements.map((name) => ({ + filename: 'test.vue', + code: `export default { components: { '${name}': {}, } }`, - parserOptions, - errors: [ - { - messageId: RESERVED_NAMES_IN_HTML.has(name) - ? 'reservedInHtml' - : 'reserved', - data: { name }, - type: 'Property', - line: 3 - } - ] - } - }), - ...vue2BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions, + errors: [ + { + messageId: RESERVED_NAMES_IN_HTML.has(name) + ? 'reservedInHtml' + : 'reserved', + data: { name }, + type: 'Property', + line: 3 + } + ] + })), + ...vue2BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVueBuiltInComponents: true }], - errors: [ - { - messageId: 'reservedInVue', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }), - ...vue2BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions, + options: [{ disallowVueBuiltInComponents: true }], + errors: [ + { + messageId: 'reservedInVue', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })), + ...vue2BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVue3BuiltInComponents: true }], - errors: [ - { - messageId: 'reservedInVue', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }), - ...vue3BuiltInComponents.map((name) => { - return { - filename: `${name}.vue`, - code: ` + parserOptions, + options: [{ disallowVue3BuiltInComponents: true }], + errors: [ + { + messageId: 'reservedInVue', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })), + ...vue3BuiltInComponents.map((name) => ({ + filename: `${name}.vue`, + code: ` export default { name: '${name}' } `, - parserOptions, - options: [{ disallowVue3BuiltInComponents: true }], - errors: [ - { - messageId: 'reservedInVue3', - data: { name }, - type: 'Literal', - line: 3 - } - ] - } - }) + parserOptions, + options: [{ disallowVue3BuiltInComponents: true }], + errors: [ + { + messageId: 'reservedInVue3', + data: { name }, + type: 'Literal', + line: 3 + } + ] + })) ] }) diff --git a/tests/lib/rules/no-restricted-call-after-await.js b/tests/lib/rules/no-restricted-call-after-await.js index 0eef6713b..69eaf3a42 100644 --- a/tests/lib/rules/no-restricted-call-after-await.js +++ b/tests/lib/rules/no-restricted-call-after-await.js @@ -170,7 +170,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 7, column: 25, endLine: 7, @@ -195,7 +195,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("foo").bar.baz` after `await` expression are forbidden.', + '`import("foo").bar.baz` is forbidden after an `await` expression.', line: 7 } ] @@ -222,12 +222,12 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 8 }, { message: - 'The `import("foo").bar.baz` after `await` expression are forbidden.', + '`import("foo").bar.baz` is forbidden after an `await` expression.', line: 9 } ] @@ -249,7 +249,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("foo").default` after `await` expression are forbidden.', + '`import("foo").default` is forbidden after an `await` expression.', line: 7 } ] @@ -271,7 +271,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("foo").default` after `await` expression are forbidden.', + '`import("foo").default` is forbidden after an `await` expression.', line: 7 } ] @@ -293,7 +293,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 7 } ] @@ -324,17 +324,17 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("./foo").bar` after `await` expression are forbidden.', + '`import("./foo").bar` is forbidden after an `await` expression.', line: 10 }, { message: - 'The `import("./baz").qux` after `await` expression are forbidden.', + '`import("./baz").qux` is forbidden after an `await` expression.', line: 11 }, { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 12 } ] @@ -365,17 +365,17 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("./foo").bar` after `await` expression are forbidden.', + '`import("./foo").bar` is forbidden after an `await` expression.', line: 10 }, { message: - 'The `import("./baz").qux` after `await` expression are forbidden.', + '`import("./baz").qux` is forbidden after an `await` expression.', line: 11 }, { message: - 'The `import("vue-i18n").useI18n` after `await` expression are forbidden.', + '`import("vue-i18n").useI18n` is forbidden after an `await` expression.', line: 12 } ] @@ -397,7 +397,7 @@ tester.run('no-restricted-call-after-await', rule, { errors: [ { message: - 'The `import("..").foo` after `await` expression are forbidden.', + '`import("..").foo` is forbidden after an `await` expression.', line: 7 } ] diff --git a/tests/lib/rules/no-unregistered-components.js b/tests/lib/rules/no-unregistered-components.js deleted file mode 100644 index f120cde81..000000000 --- a/tests/lib/rules/no-unregistered-components.js +++ /dev/null @@ -1,777 +0,0 @@ -/** - * @fileoverview Report used components that are not registered - * @author Jesús Ángel González Novez - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester -const rule = require('../../../lib/rules/no-unregistered-components') - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { - ecmaVersion: 2018, - sourceType: 'module' - } -}) - -tester.run('no-unregistered-components', rule, { - valid: [ - { - filename: 'test.vue', - code: ` - - ` - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+'] - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['Custom(\\w+)+', 'Warm(\\w+)+', 'InfoBtn(\\w+)+'] - } - ] - }, - { - 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: ` - - ` - }, - { - 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: ` - - ` - }, - { - 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: ` - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ], - errors: [ - { - message: - 'The "WarmButton" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - options: [ - { - ignorePatterns: ['custom(\\-\\w+)+'] - } - ], - errors: [ - { - message: - 'The "WarmButton" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: - 'The "CustomComponent" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: - 'The "CustomComponentWithNamedSlots" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: 'The "foo" component has been used but not registered.', - line: 3 - } - ] - }, - { - filename: 'test.vue', - code: ` - - - `, - errors: [ - { - message: 'The "foo" component has been used but not registered.', - line: 3 - } - ] - } - ] -}) diff --git a/tests/lib/rules/no-watch-after-await.js b/tests/lib/rules/no-watch-after-await.js index e97c78c63..c54c36eb7 100644 --- a/tests/lib/rules/no-watch-after-await.js +++ b/tests/lib/rules/no-watch-after-await.js @@ -188,7 +188,7 @@ tester.run('no-watch-after-await', rule, { `, errors: [ { - message: 'The `watch` after `await` expression are forbidden.', + message: '`watch` is forbidden after an `await` expression.', line: 8, column: 11, endLine: 8, diff --git a/tests/lib/rules/script-indent.js b/tests/lib/rules/script-indent.js index 229ea3273..2efb8b889 100644 --- a/tests/lib/rules/script-indent.js +++ b/tests/lib/rules/script-indent.js @@ -76,10 +76,10 @@ function loadPatterns(additionalValid, additionalInvalid) { const lines = output.split('\n').map((text, number) => ({ number, text, - indentSize: (/^[ \t]+/.exec(text) || [''])[0].length + indentSize: (/^[\t ]+/.exec(text) || [''])[0].length })) const code = lines - .map((line) => line.text.replace(/^[ \t]+/, '')) + .map((line) => line.text.replace(/^[\t ]+/, '')) .join('\n') const errors = lines .map((line) => @@ -100,8 +100,8 @@ function loadPatterns(additionalValid, additionalInvalid) { .filter(Boolean) return { - valid: valid.concat(additionalValid), - invalid: invalid.concat(additionalInvalid) + valid: [...valid, ...additionalValid], + invalid: [...invalid, ...additionalInvalid] } } diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index 5d081f468..220331b3b 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -190,7 +190,7 @@ function createInvalidTests(prefix, options, message, type) { )}bar">
`, errors: [{ message, type }], options - } + }, // We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier. // { @@ -198,8 +198,7 @@ function createInvalidTests(prefix, options, message, type) { // errors: [{ message, type }], // options // } - ].concat( - options[0] === 'always' + ...(options[0] === 'always' ? [] : [ { @@ -214,68 +213,63 @@ function createInvalidTests(prefix, options, message, type) { errors: [{ message, type }], options } - ] - ) + ]) + ] } ruleTester.run('this-in-template', rule, { - valid: ['', '', ''] - .concat(createValidTests('', [])) - .concat(createValidTests('', ['never'])) - .concat(createValidTests('this.', ['always'])) - .concat(createValidTests('this?.', ['always'])), - invalid: [] - .concat( - createInvalidTests( - 'this.', - [], - "Unexpected usage of 'this'.", - 'ThisExpression' - ), - createInvalidTests( - 'this?.', - [], - "Unexpected usage of 'this'.", - 'ThisExpression' - ) - ) - .concat( - createInvalidTests( - 'this.', - ['never'], - "Unexpected usage of 'this'.", - 'ThisExpression' - ), - createInvalidTests( - 'this?.', - ['never'], - "Unexpected usage of 'this'.", - 'ThisExpression' - ) - ) - .concat( - createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier') - ) - .concat([ - { - code: ``, - output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] - }, - { - code: ``, - output: ``, - errors: ["Unexpected usage of 'this'."], - options: ['never'] - } - ]) + valid: [ + '', + '', + '', + ...createValidTests('', []), + ...createValidTests('', ['never']), + ...createValidTests('this.', ['always']), + ...createValidTests('this?.', ['always']) + ], + invalid: [ + ...createInvalidTests( + 'this.', + [], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests( + 'this?.', + [], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests( + 'this.', + ['never'], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests( + 'this?.', + ['never'], + "Unexpected usage of 'this'.", + 'ThisExpression' + ), + ...createInvalidTests('', ['always'], "Expected 'this'.", 'Identifier'), + { + code: ``, + output: ``, + errors: ["Unexpected usage of 'this'."], + options: ['never'] + }, + { + code: ``, + output: ``, + errors: ["Unexpected usage of 'this'."], + options: ['never'] + } + ] }) function suggestionPrefix(prefix, options) { - if (options[0] === 'always' && !['this.', 'this?.'].includes(prefix)) { - return 'this.' - } else { - return '' - } + return options[0] === 'always' && !['this.', 'this?.'].includes(prefix) + ? 'this.' + : '' } diff --git a/tests/lib/rules/valid-attribute-name.js b/tests/lib/rules/valid-attribute-name.js new file mode 100644 index 000000000..920231899 --- /dev/null +++ b/tests/lib/rules/valid-attribute-name.js @@ -0,0 +1,146 @@ +/** + * @author Doug Wade + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('eslint').RuleTester +const rule = require('../../../lib/rules/valid-attribute-name') + +const tester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('valid-attribute-name', 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: `` + }, + { + 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: ``, + errors: [ + { + message: 'Attribute name 0abc is not valid.', + line: 1, + column: 14 + } + ] + }, + { + filename: 'test.vue', + code: ``, + errors: [ + { + message: 'Attribute name -def is not valid.', + line: 1, + column: 14 + } + ] + }, + { + filename: 'test.vue', + code: ``, + errors: [ + { + message: 'Attribute name !ghi is not valid.', + line: 1, + column: 14 + } + ] + }, + { + filename: 'test.vue', + code: ``, + errors: [ + { + message: 'Attribute name 0abc is not valid.', + line: 1, + column: 14 + } + ] + }, + { + filename: 'test.vue', + code: ``, + errors: [ + { + message: 'Attribute name 0abc is not valid.', + line: 1, + column: 14 + } + ] + } + ] +}) diff --git a/tests/lib/rules/valid-model-definition.js b/tests/lib/rules/valid-model-definition.js new file mode 100644 index 000000000..d9d8263ab --- /dev/null +++ b/tests/lib/rules/valid-model-definition.js @@ -0,0 +1,151 @@ +/** + * @fileoverview Prevents invalid keys in model option. + * @author Alex Sokolov + */ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const rule = require('../../../lib/rules/valid-model-definition') +const RuleTester = require('eslint').RuleTester + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 2018, + sourceType: 'module' + } +}) +ruleTester.run('valid-model-definition', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + export default { + model: { + prop: 'list' + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + event: 'update' + } + } + ` + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + prop: 'list', + event: 'update' + } + } + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + export default { + model: { + props: 'list' + } + } + `, + errors: ["Invalid key 'props' in model option."] + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + events: 'update' + } + } + `, + errors: ["Invalid key 'events' in model option."] + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + props: 'list', + event: 'update' + } + } + `, + errors: ["Invalid key 'props' in model option."] + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + prop: 'list', + events: 'update' + } + } + `, + errors: ["Invalid key 'events' in model option."] + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + props: 'list', + events: 'update' + } + } + `, + errors: [ + "Invalid key 'props' in model option.", + "Invalid key 'events' in model option." + ] + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + prop: 'checked', + props: 'list', + event: 'update' + } + } + `, + errors: ["Invalid key 'props' in model option."] + }, + { + filename: 'test.vue', + code: ` + export default { + model: { + name: 'checked', + props: 'list', + event: 'update' + } + } + `, + errors: [ + "Invalid key 'name' in model option.", + "Invalid key 'props' in model option." + ] + } + ] +}) diff --git a/tests/lib/script-setup-vars.js b/tests/lib/script-setup-vars.js new file mode 100644 index 000000000..90921984f --- /dev/null +++ b/tests/lib/script-setup-vars.js @@ -0,0 +1,384 @@ +'use strict' + +// ------------------------------------------------------------------------------ +// Requirements +// ------------------------------------------------------------------------------ + +const eslint = require('eslint') +const rules = new eslint.Linter().getRules() +const ruleNoUnusedVars = rules.get('no-unused-vars') +const ruleNoUndef = rules.get('no-undef') + +const RuleTester = eslint.RuleTester +const ruleTester = new RuleTester({ + parser: require.resolve('vue-eslint-parser'), + parserOptions: { + ecmaVersion: 6, + sourceType: 'module' + } +}) + +// ------------------------------------------------------------------------------ +// Tests +// ------------------------------------------------------------------------------ + +describe('vue-eslint-parser should properly mark the variables used in the template', () => { + ruleTester.run('no-unused-vars', ruleNoUnusedVars, { + valid: [ + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + + // Resolve component name + { + filename: 'test.vue', + code: ` + + + + ` + }, + { + filename: 'test.vue', + code: ` + + + + ` + }, + + // TopLevel await + { + filename: 'test.vue', + code: ` + + + + `, + parserOptions: { + ecmaVersion: 2022, + sourceType: 'module' + } + }, + + // ref + { + filename: 'test.vue', + code: ` + + + + ` + }, + + //style vars + { + filename: 'test.vue', + code: ` + + + + ` + }, + // ns + { + filename: 'test.vue', + code: ` + + + + ` + } + ], + + invalid: [ + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'Bar' is defined but never used.", + line: 5 + }, + { + message: "'baz' is assigned a value but never used.", + line: 18 + } + ] + }, + + // Resolve component name + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'camelCase' is defined but never used.", + line: 3 + } + ] + }, + + // Scope tests + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'msg' is assigned a value but never used.", + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + + `, + errors: [ + { + message: "'i' is assigned a value but never used.", + line: 3 + } + ] + }, + + // Not ` + + + `, + errors: [ + { + message: "'msg' is assigned a value but never used.", + line: 3 + } + ] + }, + + //style vars + { + filename: 'test.vue', + code: ` + + + + `, + errors: ["'color' is assigned a value but never used."] + } + ] + }) + + ruleTester.run('no-undef', ruleNoUndef, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: "'defineUnknown' is not defined.", + line: 3 + }, + { + message: "'defineUnknown' is not defined.", + line: 7 + } + ] + } + ] + }) +}) diff --git a/tests/lib/script-setup.js b/tests/lib/script-setup.js deleted file mode 100644 index ad7409dcb..000000000 --- a/tests/lib/script-setup.js +++ /dev/null @@ -1,49 +0,0 @@ -/** - * @author Yosuke Ota - * See LICENSE file in root directory for full license. - */ -'use strict' - -const Linter = require('eslint').Linter -const parser = require('vue-eslint-parser') -const assert = require('assert') -const experimentalScriptSetupVars = require('../../lib/rules/experimental-script-setup-vars') - -const baseConfig = { - parser: 'vue-eslint-parser', - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module' - } -} - -describe('script-setup test cases', () => { - const linter = new Linter() - linter.defineParser('vue-eslint-parser', parser) - linter.defineRule( - 'vue/experimental-script-setup-vars', - experimentalScriptSetupVars - ) - - describe('temporary supports.', () => { - const config = Object.assign({}, baseConfig, { - globals: { console: false }, - rules: { - 'vue/experimental-script-setup-vars': 'error', - 'no-undef': 'error' - } - }) - - it('should not be marked.', () => { - const code = ` - ` - const messages = linter.verify(code, config, 'test.vue') - assert.deepStrictEqual(messages, []) - }) - }) -}) diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js index 89c81942d..9b02ba46b 100644 --- a/tests/lib/utils/index.js +++ b/tests/lib/utils/index.js @@ -4,12 +4,11 @@ const espree = require('espree') const utils = require('../../../lib/utils/index') const assert = require('assert') -describe('getComputedProperties', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } +function parse(code) { + return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0].init +} +describe('getComputedProperties', () => { it('should return empty array when there is no computed property', () => { const node = parse(`const test = { name: 'test', @@ -111,11 +110,6 @@ describe('getComputedProperties', () => { }) describe('getStaticPropertyName', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - it('should parse property expression with identifier', () => { const node = parse(`const test = { computed: { } }`) @@ -137,11 +131,6 @@ describe('getStaticPropertyName', () => { }) describe('getStringLiteralValue', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - it('should parse literal', () => { const node = parse(`const test = { ['computed'] () {} }`) @@ -157,11 +146,6 @@ describe('getStringLiteralValue', () => { }) describe('getMemberChaining', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - const jsonIgnoreKeys = ['expression', 'object'] it('should parse MemberExpression', () => { @@ -276,11 +260,6 @@ describe('getMemberChaining', () => { }) describe('getRegisteredComponents', () => { - const parse = function (code) { - return espree.parse(code, { ecmaVersion: 2020 }).body[0].declarations[0] - .init - } - it('should return empty array when there are no components registered', () => { const node = parse(`const test = { name: 'test', @@ -336,15 +315,13 @@ describe('getRegisteredComponents', () => { }) }) -describe('getComponentProps', () => { - const parse = function (code) { - const data = espree.parse(code, { ecmaVersion: 2020 }).body[0] - .declarations[0].init - return utils.getComponentProps(data) - } +function parseProps(code) { + return utils.getComponentPropsFromOptions(parse(code)) +} +describe('getComponentProps', () => { it('should return empty array when there is no component props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', data() { return {} @@ -355,7 +332,7 @@ describe('getComponentProps', () => { }) it('should return empty array when component props is empty array', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', props: [] }`) @@ -364,7 +341,7 @@ describe('getComponentProps', () => { }) it('should return empty array when component props is empty object', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', props: {} }`) @@ -373,7 +350,7 @@ describe('getComponentProps', () => { }) it('should return computed props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', ...test, data() { @@ -412,7 +389,7 @@ describe('getComponentProps', () => { }) it('should return computed from array props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', data() { return {} diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js index 91da3d28c..c2fb7aaf5 100644 --- a/tests/lib/utils/vue-component.js +++ b/tests/lib/utils/vue-component.js @@ -255,7 +255,7 @@ function invalidTests(ext) { // ${ext} `, parserOptions, - errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(4)]) + errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(4)] }, { filename: `test.${ext}`, @@ -293,7 +293,7 @@ function invalidTests(ext) { // ${ext} `, parserOptions, - errors: (ext === 'js' ? [] : [makeError(3)]).concat([makeError(6)]) + errors: [...(ext === 'js' ? [] : [makeError(3)]), makeError(6)] }, { filename: `test.${ext}`, @@ -310,7 +310,7 @@ function invalidTests(ext) { // ${ext} `, parserOptions, - errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(8)]) + errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(8)] }, { filename: `test.${ext}`, @@ -332,11 +332,11 @@ ruleTester.run('vue-component', rule, { filename: 'test.js', code: `export default { }`, parserOptions - } - ] - .concat(validTests('js')) - .concat(validTests('jsx')) - .concat(validTests('vue')), + }, + ...validTests('js'), + ...validTests('jsx'), + ...validTests('vue') + ], invalid: [ { filename: 'test.vue', @@ -349,9 +349,9 @@ ruleTester.run('vue-component', rule, { code: `export default { }`, parserOptions, errors: [makeError(1)] - } + }, + ...invalidTests('js'), + ...invalidTests('jsx'), + ...invalidTests('vue') ] - .concat(invalidTests('js')) - .concat(invalidTests('jsx')) - .concat(invalidTests('vue')) }) diff --git a/tools/lib/categories.js b/tools/lib/categories.js index 7223fd87b..1d8550fb3 100644 --- a/tools/lib/categories.js +++ b/tools/lib/categories.js @@ -77,4 +77,4 @@ module.exports = categoryIds (rule) => !rule.meta.deprecated ) })) - .filter((category) => category.rules.length >= 1) + .filter((category) => category.rules.length > 0) diff --git a/tools/lib/utils.js b/tools/lib/utils.js new file mode 100644 index 000000000..eb3f53fd7 --- /dev/null +++ b/tools/lib/utils.js @@ -0,0 +1,42 @@ +module.exports = { getPresetIds, formatItems } + +const presetCategories = { + base: null, + essential: 'base', + 'vue3-essential': 'base', + 'strongly-recommended': 'essential', + 'vue3-strongly-recommended': 'vue3-essential', + recommended: 'strongly-recommended', + 'vue3-recommended': 'vue3-strongly-recommended' + // 'use-with-caution': 'recommended', + // 'vue3-use-with-caution': 'vue3-recommended' +} + +function formatItems(items, suffix) { + if (items.length === 1) { + return `${items[0]}${suffix ? ` ${suffix[0]}` : ''}` + } + if (items.length === 2) { + return `${items.join(' and ')}${suffix ? ` ${suffix[1]}` : ''}` + } + return `all of ${items.slice(0, -1).join(', ')} and ${[...items].pop()}${ + suffix ? ` ${suffix[1]}` : '' + }` +} + +function getPresetIds(categoryIds) { + const subsetCategoryIds = [] + for (const categoryId of categoryIds) { + for (const [subsetCategoryId, supersetCategoryId] of Object.entries( + presetCategories + )) { + if (supersetCategoryId === categoryId) { + subsetCategoryIds.push(subsetCategoryId) + } + } + } + if (subsetCategoryIds.length === 0) { + return categoryIds + } + return [...new Set([...categoryIds, ...getPresetIds(subsetCategoryIds)])] +} diff --git a/tools/update-docs-rules-index.js b/tools/update-docs-rules-index.js index 9b177d7ac..3db26c36b 100644 --- a/tools/update-docs-rules-index.js +++ b/tools/update-docs-rules-index.js @@ -7,9 +7,19 @@ const fs = require('fs') const path = require('path') const rules = require('./lib/rules') -const categories = require('./lib/categories') +const { getPresetIds, formatItems } = require('./lib/utils') +const removedRules = require('../lib/removed-rules') + +const VUE3_EMOJI = ':three:' +const VUE2_EMOJI = ':two:' // ----------------------------------------------------------------------------- +const categorizedRules = rules.filter( + (rule) => + rule.meta.docs.categories && + !rule.meta.docs.extensionRule && + !rule.meta.deprecated +) const uncategorizedRules = rules.filter( (rule) => !rule.meta.docs.categories && @@ -24,16 +34,23 @@ const uncategorizedExtensionRule = rules.filter( ) const deprecatedRules = rules.filter((rule) => rule.meta.deprecated) -function toRuleRow(rule) { +const TYPE_MARK = { + problem: ':warning:', + suggestion: ':hammer:', + layout: ':lipstick:' +} + +function toRuleRow(rule, kindMarks = []) { const mark = [ rule.meta.fixable ? ':wrench:' : '', rule.meta.hasSuggestions ? ':bulb:' : '', rule.meta.deprecated ? ':warning:' : '' ].join('') + const kindMark = [...kindMarks, TYPE_MARK[rule.meta.type]].join('') const link = `[${rule.ruleId}](./${rule.name}.md)` const description = rule.meta.docs.description || '(no description)' - return `| ${link} | ${description} | ${mark} |` + return `| ${link} | ${description} | ${mark} | ${kindMark} |` } function toDeprecatedRuleRow(rule) { @@ -46,29 +63,117 @@ function toDeprecatedRuleRow(rule) { return `| ${link} | ${replacedBy || '(no replacement)'} |` } +function toRemovedRuleRow({ + ruleName, + replacedBy, + deprecatedInVersion, + removedInVersion +}) { + const link = `[vue/${ruleName}](./${ruleName}.md)` + const replacement = + replacedBy.map((name) => `[vue/${name}](./${name}.md)`).join(', ') || + '(no replacement)' + const deprecatedVersionLink = `[${deprecatedInVersion}](https://github.com/vuejs/eslint-plugin-vue/releases/tag/${deprecatedInVersion})` + const removedVersionLink = `[${removedInVersion}](https://github.com/vuejs/eslint-plugin-vue/releases/tag/${removedInVersion})` + + return `| ${link} | ${replacement} | ${deprecatedVersionLink} | ${removedVersionLink} |` +} + +const categoryGroups = [ + { + title: 'Base Rules (Enabling Correct ESLint Parsing)', + description: + 'Rules in this category are enabled for all presets provided by eslint-plugin-vue.', + categoryIdForVue3: 'base', + categoryIdForVue2: 'base', + useMark: false + }, + { + title: 'Priority A: Essential (Error Prevention)', + categoryIdForVue3: 'vue3-essential', + categoryIdForVue2: 'essential', + useMark: true + }, + { + title: 'Priority B: Strongly Recommended (Improving Readability)', + categoryIdForVue3: 'vue3-strongly-recommended', + categoryIdForVue2: 'strongly-recommended', + useMark: true + }, + { + title: 'Priority C: Recommended (Potentially Dangerous Patterns)', + categoryIdForVue3: 'vue3-recommended', + categoryIdForVue2: 'recommended', + useMark: true + } +] + // ----------------------------------------------------------------------------- -let rulesTableContent = categories - .map( - (category) => ` -## ${category.title.vuepress} +let rulesTableContent = categoryGroups + .map((group) => { + const rules = categorizedRules.filter( + (rule) => + rule.meta.docs.categories.includes(group.categoryIdForVue3) || + rule.meta.docs.categories.includes(group.categoryIdForVue2) + ) + let content = ` +## ${group.title} +` -Enforce all the rules in this category, as well as all higher priority rules, with: + if (group.description) { + content += ` +${group.description} +` + } + if (group.useMark) { + const presetsForVue3 = getPresetIds([group.categoryIdForVue3]).map( + (categoryId) => `\`"plugin:vue/${categoryId}"\`` + ) + const presetsForVue2 = getPresetIds([group.categoryIdForVue2]).map( + (categoryId) => `\`"plugin:vue/${categoryId}"\`` + ) + content += ` +- ${VUE3_EMOJI} Indicates that the rule is for Vue 3 and is included in ${formatItems( + presetsForVue3, + ['preset', 'presets'] + )}. +- ${VUE2_EMOJI} Indicates that the rule is for Vue 2 and is included in ${formatItems( + presetsForVue2, + ['preset', 'presets'] + )}. +` + } -\`\`\`json -{ - "extends": "plugin:vue/${category.categoryId}" -} -\`\`\` + content += ` + -| Rule ID | Description | | -|:--------|:------------|:---| -${category.rules.map(toRuleRow).join('\n')} +| Rule ID | Description | | | +|:--------|:------------|:--:|:--:| +${rules + .map((rule) => { + const mark = group.useMark + ? [ + rule.meta.docs.categories.includes(group.categoryIdForVue3) + ? [VUE3_EMOJI] + : [], + rule.meta.docs.categories.includes(group.categoryIdForVue2) + ? [VUE2_EMOJI] + : [] + ].flat() + : [] + return toRuleRow(rule, mark) + }) + .join('\n')} + + ` - ) + + return content + }) .join('') // ----------------------------------------------------------------------------- -if (uncategorizedRules.length || uncategorizedExtensionRule.length) { +if (uncategorizedRules.length > 0 || uncategorizedExtensionRule.length > 0) { rulesTableContent += ` ## Uncategorized @@ -88,27 +193,35 @@ For example: \`\`\` ` } -if (uncategorizedRules.length) { +if (uncategorizedRules.length > 0) { rulesTableContent += ` -| Rule ID | Description | | -|:--------|:------------|:---| -${uncategorizedRules.map(toRuleRow).join('\n')} + + +| Rule ID | Description | | | +|:--------|:------------|:--:|:--:| +${uncategorizedRules.map((rule) => toRuleRow(rule)).join('\n')} + + ` } -if (uncategorizedExtensionRule.length) { +if (uncategorizedExtensionRule.length > 0) { rulesTableContent += ` ### Extension Rules The following rules extend the rules provided by ESLint itself and apply them to the expressions in the \`