diff --git a/.changeset/README.md b/.changeset/README.md new file mode 100644 index 000000000..e5b6d8d6a --- /dev/null +++ b/.changeset/README.md @@ -0,0 +1,8 @@ +# Changesets + +Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works +with multi-package repos, or single-package repos to help you version and publish your code. You can +find the full documentation for it [in our repository](https://github.com/changesets/changesets) + +We have a quick list of common questions to get you started engaging with this project in +[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md) diff --git a/.changeset/config.json b/.changeset/config.json new file mode 100644 index 000000000..43b72b358 --- /dev/null +++ b/.changeset/config.json @@ -0,0 +1,15 @@ +{ + "$schema": "https://unpkg.com/@changesets/config/schema.json", + "changelog": [ + "@svitejs/changesets-changelog-github-compact", + { + "repo": "vuejs/eslint-plugin-vue" + } + ], + "commit": false, + "linked": [], + "access": "public", + "baseBranch": "master", + "bumpVersionsWithWorkspaceProtocolOnly": true, + "ignore": [] +} diff --git a/.changeset/smooth-jokes-eat.md b/.changeset/smooth-jokes-eat.md new file mode 100644 index 000000000..e5f3a14a8 --- /dev/null +++ b/.changeset/smooth-jokes-eat.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-vue": patch +--- + +Updates resources diff --git a/.changeset/true-oranges-heal.md b/.changeset/true-oranges-heal.md new file mode 100644 index 000000000..eba8b9a99 --- /dev/null +++ b/.changeset/true-oranges-heal.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-vue': patch +--- + +[vue/no-restricted-html-elements](https://eslint.vuejs.org/rules/no-restricted-html-elements.html) now also checks SVG and MathML elements. diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index e695f422a..000000000 --- a/.circleci/config.yml +++ /dev/null @@ -1,124 +0,0 @@ -workflows: - version: 2 - node-multi-build: - jobs: - - eslint-v6 - - eslint-v7 - - ts-eslint-v4 - - node-v12 - - node-v14 - - node-v16 - - lint - -version: 2 -jobs: - node-base: &node-base - docker: - - image: node - steps: - - run: - name: Versions - command: npm version - - checkout - # - restore_cache: - # keys: - # - v2-npm-lock-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }} - - run: - name: Install dependencies - command: npm install --legacy-peer-deps - - run: - name: Test - command: npm test - # - save_cache: - # key: v2-npm-lock-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }} - # paths: - # - node_modules - - eslint-v6: - docker: - - image: node:12 - steps: - - run: - name: Versions - command: npm version - - checkout - - run: - name: Install eslint@6 - command: | - npm install --save-exact eslint@6.8.0 - - run: - name: Install dependencies - command: npm install - - run: - name: Test - command: npm test - eslint-v7: - docker: - - image: node:14 - steps: - - run: - name: Versions - command: npm version - - checkout - - run: - name: Install eslint@7 - command: | - npm install eslint@7 - - run: - name: Install dependencies - command: npm install - - run: - name: Test - command: npm test - ts-eslint-v4: - docker: - - image: node:14 - steps: - - run: - name: Versions - command: npm version - - checkout - - run: - name: Install @typescript-eslint/parser@4 eslint@7 - command: | - npm install @typescript-eslint/parser@^4 eslint@7 - - run: - name: Install dependencies - command: npm install - - run: - name: Test - command: npm test - node-v12: - <<: *node-base - docker: - - image: node:12 - node-v14: - <<: *node-base - docker: - - image: node:14 - node-v16: - <<: *node-base - docker: - - image: node:16 - - lint: - docker: - - image: node:14 - steps: - - run: - name: Versions - command: npm version - - checkout - - restore_cache: - keys: - - v2-npm-lock-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }} - - run: - name: Install dependencies - command: npm install - - save_cache: - key: v2-npm-lock-{{ .Branch }}-{{ .Environment.CIRCLE_JOB }}-{{ checksum "package.json" }} - paths: - - node_modules - - run: - name: Lint - command: npm run lint diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 1905aa4a8..000000000 --- a/.eslintignore +++ /dev/null @@ -1,8 +0,0 @@ -/.nyc_output -/coverage -/node_modules -/tests/fixtures -/tests/integrations/eslint-plugin-import - -!.vuepress -/docs/.vuepress/dist diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 3229de98d..000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,158 +0,0 @@ -'use strict' - -module.exports = { - root: true, - parserOptions: { - ecmaVersion: 2018 - }, - env: { - es6: true, - node: true, - mocha: true - }, - extends: [ - 'plugin:eslint-plugin/recommended', - 'prettier', - 'plugin:node-dependencies/recommended', - 'plugin:jsonc/recommended-with-jsonc' - ], - plugins: ['eslint-plugin', 'prettier'], - rules: { - 'accessor-pairs': 2, - camelcase: [2, { properties: 'never' }], - 'constructor-super': 2, - eqeqeq: [2, 'allow-null'], - 'handle-callback-err': [2, '^(err|error)$'], - 'jsx-quotes': [2, 'prefer-single'], - 'new-cap': [2, { newIsCap: true, capIsNew: false }], - 'new-parens': 2, - 'no-array-constructor': 2, - 'no-caller': 2, - 'no-class-assign': 2, - 'no-cond-assign': 2, - 'no-const-assign': 2, - 'no-control-regex': 2, - 'no-delete-var': 2, - 'no-dupe-args': 2, - 'no-dupe-class-members': 2, - 'no-dupe-keys': 2, - 'no-duplicate-case': 2, - 'no-empty-character-class': 2, - 'no-empty-pattern': 2, - 'no-eval': 2, - 'no-ex-assign': 2, - 'no-extend-native': 2, - 'no-extra-bind': 2, - 'no-extra-boolean-cast': 2, - 'no-extra-parens': [2, 'functions'], - 'no-fallthrough': 2, - 'no-floating-decimal': 2, - 'no-func-assign': 2, - 'no-implied-eval': 2, - 'no-inner-declarations': [2, 'functions'], - 'no-invalid-regexp': 2, - 'no-irregular-whitespace': 2, - 'no-iterator': 2, - 'no-label-var': 2, - 'no-labels': [2, { allowLoop: false, allowSwitch: false }], - 'no-lone-blocks': 2, - 'no-multi-spaces': [2, { ignoreEOLComments: true }], - 'no-multi-str': 2, - 'no-native-reassign': 2, - 'no-negated-in-lhs': 2, - 'no-new-object': 2, - 'no-new-require': 2, - 'no-new-symbol': 2, - 'no-new-wrappers': 2, - 'no-obj-calls': 2, - 'no-octal': 2, - 'no-octal-escape': 2, - 'no-path-concat': 2, - 'no-proto': 2, - 'no-redeclare': 2, - 'no-regex-spaces': 2, - 'no-return-assign': [2, 'except-parens'], - 'no-self-assign': 2, - 'no-self-compare': 2, - 'no-sequences': 2, - 'no-shadow-restricted-names': 2, - 'no-sparse-arrays': 2, - 'no-this-before-super': 2, - 'no-throw-literal': 2, - 'no-undef': 2, - 'no-undef-init': 2, - 'no-unexpected-multiline': 2, - 'no-unmodified-loop-condition': 2, - 'no-unneeded-ternary': [2, { defaultAssignment: false }], - 'no-unreachable': 2, - 'no-unsafe-finally': 2, - 'no-unused-vars': [2, { vars: 'all', args: 'none' }], - 'no-useless-call': 2, - 'no-useless-computed-key': 2, - 'no-useless-constructor': 2, - 'no-useless-escape': 0, - 'no-with': 2, - 'one-var': [2, { initialized: 'never' }], - 'use-isnan': 2, - 'valid-typeof': 2, - 'wrap-iife': [2, 'any'], - yoda: [2, 'never'], - 'prefer-const': 2, - - 'prettier/prettier': 'error', - 'eslint-plugin/report-message-format': ['error', "^[A-Z`'{].*\\.$"], - 'eslint-plugin/prefer-placeholders': 'error', - 'eslint-plugin/consistent-output': 'error', - - 'no-debugger': 'error', - 'no-console': 'error', - 'no-alert': 'error', - 'no-void': 'error', - - 'no-warning-comments': 'warn', - 'no-var': 'error', - 'prefer-template': 'error', - 'object-shorthand': 'error', - 'prefer-rest-params': 'error', - 'prefer-arrow-callback': 'error', - 'prefer-spread': 'error', - - 'dot-notation': 'error' - }, - overrides: [ - { - files: ['./**/*.vue'], - parser: require.resolve('vue-eslint-parser'), - parserOptions: { - ecmaVersion: 2020, - sourceType: 'module' - } - }, - { - files: ['lib/rules/*.js'], - rules: { - 'eslint-plugin/no-deprecated-context-methods': 'error', - 'eslint-plugin/no-only-tests': 'error', - 'eslint-plugin/prefer-object-rule': 'error', - 'eslint-plugin/require-meta-docs-description': 'error', - 'eslint-plugin/require-meta-docs-url': [ - 'error', - { - pattern: `https://eslint.vuejs.org/rules/{{name}}.html` - } - ], - 'eslint-plugin/require-meta-has-suggestions': 'error', - 'eslint-plugin/require-meta-schema': 'error', - 'eslint-plugin/require-meta-type': 'error', - 'no-invalid-meta': 'error', - 'no-invalid-meta-docs-categories': 'error' - } - }, - { - files: ['*.json'], - rules: { - 'prettier/prettier': 'off' - } - } - ] -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml new file mode 100644 index 000000000..c4d031e9c --- /dev/null +++ b/.github/FUNDING.yml @@ -0,0 +1,3 @@ +github: + - ota-meshi + - FloEdelmann diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index f1169f466..08d755127 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -26,6 +26,7 @@ about: Create a report to help us improve - **ESLint version:** - **eslint-plugin-vue version:** +- **Vue version:** - **Node version:** - **Operating System:** diff --git a/.github/ISSUE_TEMPLATE/change.md b/.github/ISSUE_TEMPLATE/change.md index a6a4a7dc0..02397f6cd 100644 --- a/.github/ISSUE_TEMPLATE/change.md +++ b/.github/ISSUE_TEMPLATE/change.md @@ -13,6 +13,7 @@ about: Request a change that is not a bug fix, rule change, or new rule - **ESLint version:** - **eslint-plugin-vue version:** +- **Vue version:** - **Node version:** **The problem you want to solve.** diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml new file mode 100644 index 000000000..57aa5876d --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,68 @@ +name: CI +on: + push: + branches: + - 'master' + pull_request: + types: + - 'opened' + - 'synchronize' + - 'reopened' + +permissions: + contents: read + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + - name: Install Packages + run: npm install --legacy-peer-deps + - name: Lint + run: npm run lint + + test: + name: Test + strategy: + matrix: + node: [18, 20, 21, 'lts/*'] + eslint: [9] + include: + # On old ESLint version + - node: 18 + eslint: 8 + + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js v${{ matrix.node }} + uses: actions/setup-node@v4 + with: + node-version: ${{ matrix.node }} + - name: Install Packages + run: npm install -f + - name: Install ESLint v${{ matrix.eslint }} + run: npm install --save-dev eslint@${{ matrix.eslint }} -f + - name: Test + run: npm test + + test-without-eslint-stylistic: + name: Test without ESLint Stylistic + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + - name: Install Packages + run: npm install -f + - name: Uninstall @stylistic/eslint-plugin + run: npm uninstall -D @stylistic/eslint-plugin + - name: Test + run: npm test diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml new file mode 100644 index 000000000..260b73582 --- /dev/null +++ b/.github/workflows/Release.yml @@ -0,0 +1,35 @@ +name: Release + +on: + push: + branches: + - master + +permissions: {} + +jobs: + release: + # prevents this action from running on forks + if: github.repository == 'vuejs/eslint-plugin-vue' + permissions: + contents: write # to create release (changesets/action) + pull-requests: write # to create pull request (changesets/action) + name: Release + runs-on: ubuntu-latest + steps: + - name: Checkout Repo + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + - name: Install Dependencies + run: npm install -f + + - name: Create Release Pull Request or Publish to npm + id: changesets + uses: changesets/action@v1 + with: + version: npm run changeset:version + publish: npm run changeset:publish + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + NPM_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/.github/workflows/check-for-resources-update.yml b/.github/workflows/check-for-resources-update.yml new file mode 100644 index 000000000..31f881b1d --- /dev/null +++ b/.github/workflows/check-for-resources-update.yml @@ -0,0 +1,31 @@ +name: Check for utils resources update +on: + workflow_dispatch: null + schedule: + - cron: 0 0 * * 0 # At 00:00 on Sunday, see https://crontab.guru/#0_0_*_*_0 + +permissions: + contents: write + pull-requests: write + +jobs: + check-for-resources-update: + runs-on: ubuntu-latest + if: ${{ github.repository == 'vuejs/eslint-plugin-vue' }} + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install Packages + run: npm install + - name: Update + run: npm run update-resources + - uses: peter-evans/create-pull-request@v7 + with: + commit-message: Updates resources + branch: update-resources + branch-suffix: timestamp + title: Updates resources diff --git a/.gitignore b/.gitignore index e1401b951..797d0cbcd 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,9 @@ /test.* yarn.lock yarn-error.log -docs/.vuepress/dist +/docs/.vitepress/dist +/docs/.vitepress/build-system/shim/eslint.mjs +/docs/.vitepress/build-system/shim/assert.mjs +/docs/.vitepress/.temp +/docs/.vitepress/cache typings/eslint/lib/rules diff --git a/.markdownlint.yml b/.markdownlint.yml new file mode 100644 index 000000000..6591a3543 --- /dev/null +++ b/.markdownlint.yml @@ -0,0 +1,26 @@ +line-length: false +link-fragments: false +single-title: false +no-inline-html: + allowed_elements: + - badge + - eslint-code-block + - sup + - rules-table + - span + +# enforce consistency +code-block-style: + style: fenced +code-fence-style: + style: backtick +emphasis-style: + style: underscore +heading-style: + style: atx +hr-style: + style: --- +strong-style: + style: asterisk +ul-style: + style: dash diff --git a/.markdownlintignore b/.markdownlintignore new file mode 100644 index 000000000..e7becf85b --- /dev/null +++ b/.markdownlintignore @@ -0,0 +1,2 @@ +node_modules +CHANGELOG.md diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 000000000..d341f1772 --- /dev/null +++ b/.prettierignore @@ -0,0 +1,2 @@ +tests/fixtures/ +.github/ISSUE_TEMPLATE/*.md diff --git a/.vscode/launch.json b/.vscode/launch.json index 324a79864..2fa4db9b2 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -6,10 +6,7 @@ "request": "launch", "name": "Start testing", "program": "${workspaceFolder}/node_modules/.bin/mocha", - "args": [ - "${file}", - "--watch" - ], + "args": ["${file}", "--watch"], "console": "integratedTerminal" } ] diff --git a/.vscode/settings.json b/.vscode/settings.json index 7bb646536..f80681b66 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,19 +1,15 @@ { - "editor.tabSize": 2, - "eslint.options": { - "rulePaths": ["eslint-internal-rules"] - }, - "eslint.validate": [ - "javascript", - "javascriptreact", - "vue", - "json", - "jsonc" - ], - "typescript.tsdk": "node_modules/typescript/lib", - "vetur.validation.script": false, - "[typescript]": { - "editor.formatOnSave": true, - "editor.defaultFormatter": "esbenp.prettier-vscode" - }, + "editor.tabSize": 2, + "eslint.experimental.useFlatConfig": true, + "eslint.validate": ["javascript", "javascriptreact", "vue", "json", "jsonc"], + "typescript.tsdk": "./node_modules/typescript/lib", + "vetur.validation.script": false, + "[typescript]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" + }, + "[javascript]": { + "editor.formatOnSave": true, + "editor.defaultFormatter": "esbenp.prettier-vscode" + } } diff --git a/CHANGELOG.md b/CHANGELOG.md new file mode 100644 index 000000000..b21ab6f6f --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,11 @@ +# eslint-plugin-vue + +## 10.2.0 + +### Minor Changes + +- [vue/no-restricted-html-elements](https://eslint.vuejs.org/rules/no-restricted-html-elements.html) now accepts multiple elements in each entry. ([#2750](https://github.com/vuejs/eslint-plugin-vue/pull/2750)) + +### Patch Changes + +- Updates resources ([#2747](https://github.com/vuejs/eslint-plugin-vue/pull/2747)) diff --git a/README.md b/README.md index e3e50f284..bdeb5e167 100644 --- a/README.md +++ b/README.md @@ -2,8 +2,8 @@ [![NPM version](https://img.shields.io/npm/v/eslint-plugin-vue.svg?style=flat)](https://npmjs.org/package/eslint-plugin-vue) [![NPM downloads](https://img.shields.io/npm/dm/eslint-plugin-vue.svg?style=flat)](https://npmjs.org/package/eslint-plugin-vue) -[![CircleCI](https://img.shields.io/circleci/project/github/vuejs/eslint-plugin-vue/master.svg?style=flat)](https://circleci.com/gh/vuejs/eslint-plugin-vue) -[![License](https://img.shields.io/github/license/vuejs/eslint-plugin-vue.svg?style=flat)](https://github.com/vuejs/eslint-plugin-vue/blob/master/LICENSE.md) +[![CI](https://img.shields.io/github/actions/workflow/status/vuejs/eslint-plugin-vue/CI.yml?style=flat&label=CI)](https://github.com/vuejs/eslint-plugin-vue/actions/workflows/CI.yml) +[![License](https://img.shields.io/github/license/vuejs/eslint-plugin-vue.svg?style=flat)](https://github.com/vuejs/eslint-plugin-vue/blob/master/LICENSE) > Official ESLint plugin for Vue.js @@ -13,7 +13,14 @@ Please refer to the [official website](https://eslint.vuejs.org). ## :anchor: Versioning Policy -This plugin follows [Semantic Versioning](https://semver.org) and [ESLint's Semantic Versioning Policy](https://github.com/eslint/eslint#semantic-versioning-policy). +This plugin follows [Semantic Versioning]. +However, please note that we do not follow [ESLint's Semantic Versioning Policy]. +In minor version releases, this plugin may change the sharable configs provided by the plugin or the default behavior of the plugin's rules in order to add features to the plugin. Because we want to add many features to the plugin soon, so that users can easily take advantage of new features in Vue and Nuxt. + +According to our policy, any minor update may report more linting errors than the previous release. As such, we recommend using the [tilde (`~`)](https://semver.npmjs.com/#syntax-examples) in `package.json` to guarantee the results of your builds. + +[Semantic Versioning]: https://semver.org/ +[ESLint's Semantic Versioning Policy]: https://github.com/eslint/eslint#semantic-versioning-policy ## :newspaper: Releases @@ -29,7 +36,7 @@ Be sure to read the [official ESLint guide](https://eslint.org/docs/developer-gu To see what an abstract syntax tree (AST) of your code looks like, you may use [AST Explorer](https://astexplorer.net). After opening [AST Explorer](https://astexplorer.net), select `Vue` as the syntax and `vue-eslint-parser` as the parser. -The default JavaScript parser must be replaced because [Vue.js single file components](https://v3.vuejs.org/guide/single-file-component.html#single-file-components) are not plain JavaScript, but a custom file format. [`vue-eslint-parser`](https://github.com/vuejs/vue-eslint-parser) is a replacement parser that generates an enhanced AST with nodes that represent specific parts of the template syntax, as well as the contents of the ` + + + + diff --git a/docs/.vuepress/components/eslint-code-block.vue b/docs/.vitepress/theme/components/eslint-code-block.vue similarity index 50% rename from docs/.vuepress/components/eslint-code-block.vue rename to docs/.vitepress/theme/components/eslint-code-block.vue index 2c6abd2cc..77d4fd7cf 100644 --- a/docs/.vuepress/components/eslint-code-block.vue +++ b/docs/.vitepress/theme/components/eslint-code-block.vue @@ -3,7 +3,7 @@ diff --git a/docs/.vitepress/theme/index.ts b/docs/.vitepress/theme/index.ts new file mode 100644 index 000000000..757e63cab --- /dev/null +++ b/docs/.vitepress/theme/index.ts @@ -0,0 +1,32 @@ +// @ts-expect-error -- Browser +if (typeof window !== 'undefined') { + if (typeof require === 'undefined') { + // @ts-expect-error -- Browser + ;(window as any).require = () => { + const e = new Error('require is not defined') + ;(e as any).code = 'MODULE_NOT_FOUND' + throw e + } + } +} +// @ts-expect-error -- Cannot change `module` option +import type { Theme } from 'vitepress' +// @ts-expect-error -- Cannot change `module` option +import DefaultTheme from 'vitepress/theme' +// @ts-expect-error -- ignore +import Layout from './Layout.vue' +// @ts-expect-error -- ignore +import ESLintCodeBlock from './components/eslint-code-block.vue' +// @ts-expect-error -- ignore +import RulesTable from './components/rules-table.vue' + +const theme: Theme = { + ...DefaultTheme, + Layout, + enhanceApp(ctx) { + DefaultTheme.enhanceApp(ctx) + ctx.app.component('eslint-code-block', ESLintCodeBlock) + ctx.app.component('rules-table', RulesTable) + } +} +export default theme diff --git a/docs/.vitepress/vite-plugin.mts b/docs/.vitepress/vite-plugin.mts new file mode 100644 index 000000000..cd6811cb6 --- /dev/null +++ b/docs/.vitepress/vite-plugin.mts @@ -0,0 +1,83 @@ +import type { UserConfig } from 'vitepress' +import path from 'pathe' +import { fileURLToPath } from 'url' +import esbuild from 'esbuild' +type Plugin = Extract< + NonNullable['plugins']>[number], + { name: string } +> + +const libRoot = path.join(fileURLToPath(import.meta.url), '../../../lib') +export function vitePluginRequireResolve(): Plugin { + return { + name: 'vite-plugin-require.resolve', + transform(code, id, _options) { + if (id.startsWith(libRoot)) { + return code.replace(/require\.resolve/gu, '(function(){return 0})') + } + return undefined + } + } +} + +export function viteCommonjs(): Plugin { + return { + name: 'vite-plugin-cjs-to-esm', + apply: () => true, + async transform(code, id) { + if (!id.startsWith(libRoot)) { + return undefined + } + const base = transformRequire(code) + try { + const transformed = esbuild.transformSync(base, { + format: 'esm' + }) + return transformed.code + } catch (e) { + console.error('Transform error. base code:\n' + base, e) + } + return undefined + } + } +} + +/** + * Transform `require()` to `import` + */ +function transformRequire(code: string) { + if (!code.includes('require')) { + return code + } + const modules = new Map() + const replaced = code.replace( + /(\/\/[^\n\r]*|\/\*[\s\S]*?\*\/)|\brequire\s*\(\s*(["'].*?["'])\s*\)/gu, + (match, comment, moduleString) => { + if (comment) { + return match + } + + let id = + '__' + + moduleString.replace(/[^a-zA-Z0-9_$]+/gu, '_') + + Math.random().toString(32).substring(2) + while (code.includes(id) || modules.has(id)) { + id += Math.random().toString(32).substring(2) + } + modules.set(id, moduleString) + return id + '()' + } + ) + + return ( + [...modules] + .map(([id, moduleString]) => { + return `import * as __temp_${id} from ${moduleString}; +const ${id} = () => __temp_${id}.default || __temp_${id}; +` + }) + .join('') + + ';\n' + + replaced + ) +} diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js deleted file mode 100644 index 68aec1af9..000000000 --- a/docs/.vuepress/config.js +++ /dev/null @@ -1,192 +0,0 @@ -/** - * @author Toru Nagashima - * See LICENSE file in root directory for full license. - */ -'use strict' - -const rules = require('../../tools/lib/rules') -const path = require('path') - -const uncategorizedRules = rules.filter( - (rule) => - !rule.meta.docs.categories && - !rule.meta.docs.extensionRule && - !rule.meta.deprecated -) -const uncategorizedExtensionRule = rules.filter( - (rule) => - !rule.meta.docs.categories && - rule.meta.docs.extensionRule && - !rule.meta.deprecated -) -const deprecatedRules = rules.filter((rule) => rule.meta.deprecated) - -const sidebarCategories = [ - { title: 'Base Rules', categoryIds: ['base'] }, - { - title: 'Priority A: Essential', - categoryIds: ['vue3-essential', 'essential'] - }, - { - title: 'Priority A: Essential for Vue.js 3.x', - categoryIds: ['vue3-essential'] - }, - { title: 'Priority A: Essential for Vue.js 2.x', categoryIds: ['essential'] }, - { - title: 'Priority B: Strongly Recommended', - categoryIds: ['vue3-strongly-recommended', 'strongly-recommended'] - }, - { - title: 'Priority B: Strongly Recommended for Vue.js 3.x', - categoryIds: ['vue3-strongly-recommended'] - }, - { - title: 'Priority B: Strongly Recommended for Vue.js 2.x', - categoryIds: ['strongly-recommended'] - }, - { - title: 'Priority C: Recommended', - categoryIds: ['vue3-recommended', 'recommended'] - }, - { - title: 'Priority C: Recommended for Vue.js 3.x', - categoryIds: ['vue3-recommended'] - }, - { - title: 'Priority C: Recommended for Vue.js 2.x', - categoryIds: ['recommended'] - } -] - -const categorizedRules = [] -for (const { title, categoryIds } of sidebarCategories) { - const categoryRules = rules - .filter((rule) => rule.meta.docs.categories && !rule.meta.deprecated) - .filter((rule) => - categoryIds.every((categoryId) => - rule.meta.docs.categories.includes(categoryId) - ) - ) - const children = categoryRules - .filter(({ ruleId }) => { - const exists = categorizedRules.some(({ children }) => - children.some(([, alreadyRuleId]) => alreadyRuleId === ruleId) - ) - return !exists - }) - .map(({ ruleId, name }) => [`/rules/${name}`, ruleId]) - - if (children.length === 0) { - continue - } - categorizedRules.push({ - title, - collapsable: false, - children - }) -} - -const extraCategories = [] -if (uncategorizedRules.length > 0) { - extraCategories.push({ - title: 'Uncategorized', - collapsable: false, - children: uncategorizedRules.map(({ ruleId, name }) => [ - `/rules/${name}`, - ruleId - ]) - }) -} -if (uncategorizedExtensionRule.length > 0) { - extraCategories.push({ - title: 'Extension Rules', - collapsable: false, - children: uncategorizedExtensionRule.map(({ ruleId, name }) => [ - `/rules/${name}`, - ruleId - ]) - }) -} -if (deprecatedRules.length > 0) { - extraCategories.push({ - title: 'Deprecated', - collapsable: false, - children: deprecatedRules.map(({ ruleId, name }) => [ - `/rules/${name}`, - ruleId - ]) - }) -} - -module.exports = { - configureWebpack(_config, _isServer) { - return { - resolve: { - alias: { - module: require.resolve('./shim/module'), - eslint$: require.resolve('./shim/eslint'), - esquery: path.resolve( - __dirname, - '../../node_modules/esquery/dist/esquery.min.js' - ), - '@eslint/eslintrc/universal': path.resolve( - __dirname, - '../../node_modules/@eslint/eslintrc/dist/eslintrc-universal.cjs' - ) - } - } - } - }, - - base: '/', - title: 'eslint-plugin-vue', - description: 'Official ESLint plugin for Vue.js', - evergreen: true, - head: [['link', { rel: 'icon', href: '/favicon.png' }]], - - plugins: { - '@vuepress/pwa': { - serviceWorker: true, - updatePopup: true - } - }, - - themeConfig: { - repo: 'vuejs/eslint-plugin-vue', - docsRepo: 'vuejs/eslint-plugin-vue', - docsDir: 'docs', - docsBranch: 'master', - editLinks: true, - lastUpdated: true, - - nav: [ - { text: 'User Guide', link: '/user-guide/' }, - { text: 'Developer Guide', link: '/developer-guide/' }, - { text: 'Rules', link: '/rules/' }, - { - text: 'Demo', - link: 'https://ota-meshi.github.io/eslint-plugin-vue-demo/' - } - ], - - sidebar: { - '/rules/': [ - '/rules/', - - // Rules in each category. - ...categorizedRules, - - // Rules in no category. - ...extraCategories - ], - - '/': ['/', '/user-guide/', '/developer-guide/', '/rules/'] - }, - - algolia: { - appId: '2L4MGZSULB', - apiKey: 'fdf57932b27a6c230d01a890492ab76d', - indexName: 'eslint-plugin-vue' - } - } -} diff --git a/docs/.vuepress/enhanceApp.js b/docs/.vuepress/enhanceApp.js deleted file mode 100644 index dc7e30771..000000000 --- a/docs/.vuepress/enhanceApp.js +++ /dev/null @@ -1,28 +0,0 @@ -/* globals window */ -export default ( - // eslint-disable-next-line no-empty-pattern - { - // Vue, // the version of Vue being used in the VuePress app - // options, // the options for the root Vue instance - // router, // the router instance for the app - // 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] - } - } - ) - } - } -} diff --git a/docs/.vuepress/shim/eslint.js b/docs/.vuepress/shim/eslint.js deleted file mode 100644 index 08af6dfcd..000000000 --- a/docs/.vuepress/shim/eslint.js +++ /dev/null @@ -1,4 +0,0 @@ -const { Linter } = require('eslint/lib/linter') -module.exports = { - Linter -} diff --git a/docs/.vuepress/shim/module.js b/docs/.vuepress/shim/module.js deleted file mode 100644 index 66ab9785e..000000000 --- a/docs/.vuepress/shim/module.js +++ /dev/null @@ -1,3 +0,0 @@ -module.exports = { - createRequire: () => () => null -} diff --git a/docs/.vuepress/styles/index.styl b/docs/.vuepress/styles/index.styl deleted file mode 100644 index 48680bbf3..000000000 --- a/docs/.vuepress/styles/index.styl +++ /dev/null @@ -1,20 +0,0 @@ -.theme-container.rule-details .theme-default-content > h1 { - font-size: 1.8rem; - - + blockquote { - margin-top: -15px; - padding: 0; - border: 0; - font-weight: 500; - font-size: 1.4rem; - color: currentColor; - - ::first-letter { - text-transform: uppercase; - } - - p { - line-height: 1.2; - } - } -} diff --git a/docs/README.md b/docs/README.md deleted file mode 100644 index 672aec594..000000000 --- a/docs/README.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -sidebarDepth: 0 ---- - -# Introduction - -Official ESLint plugin for Vue.js. - -This plugin allows us to check the ``, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -165,13 +162,13 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -186,13 +183,13 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -207,13 +204,13 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -228,13 +225,13 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -249,13 +246,13 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -271,13 +268,13 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: ` `, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -292,8 +289,8 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: null, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -310,8 +307,8 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: null, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', @@ -328,8 +325,8 @@ tester.run('no-unsupported-features/v-slot', rule, { `, - options: buildOptions(), output: null, + options: buildOptions(), errors: [ { message: '`v-slot` are not supported until Vue.js "2.6.0".', diff --git a/tests/lib/rules/no-unused-components.js b/tests/lib/rules/no-unused-components.js index f63b75f13..4078af76f 100644 --- a/tests/lib/rules/no-unused-components.js +++ b/tests/lib/rules/no-unused-components.js @@ -4,20 +4,12 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-unused-components') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2018, sourceType: 'module' } @@ -421,13 +413,6 @@ tester.run('no-unused-components', rule, { ` }, - { - filename: 'test.vue', - code: ` - ` - }, // computed properties { @@ -637,26 +622,6 @@ tester.run('no-unused-components', rule, { } ] }, - { - filename: 'test.vue', - code: ` - - `, - errors: [ - { - message: 'The "Foo" component has been registered but not used.', - line: 8 - } - ] - }, // computed properties { @@ -695,6 +660,36 @@ tester.run('no-unused-components', rule, { line: 13 } ] + }, + + // Many components and one in middle is no present + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: 'The "Bar" component has been registered but not used.', + line: 14 + } + ] } ] }) diff --git a/tests/lib/rules/no-unused-emit-declarations.js b/tests/lib/rules/no-unused-emit-declarations.js new file mode 100644 index 000000000..b4469150d --- /dev/null +++ b/tests/lib/rules/no-unused-emit-declarations.js @@ -0,0 +1,789 @@ +/** + * @author ItMaga + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/no-unused-emit-declarations') +const { + getTypeScriptFixtureTestOptions +} = require('../../test-utils/typescript') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('no-unused-emit-declarations', 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: ` + + ` + }, + { + 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: [ + { + messageId: 'unused', + line: 4, + column: 26, + endColumn: 33 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 24, + endColumn: 29 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + messageId: 'unused', + line: 7, + column: 19, + endColumn: 24 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 20, + endColumn: 29 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + messageId: 'unused', + line: 6, + column: 35, + endColumn: 40 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 19, + endColumn: 24 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 26, + endColumn: 31 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + messageId: 'unused', + line: 8, + column: 19, + endColumn: 24 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 26, + endColumn: 31 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 19, + endColumn: 24 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 26, + endColumn: 31 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + endLine: 4, + column: 33, + endColumn: 38 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 3, + column: 24, + endColumn: 29 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + errors: [ + { + messageId: 'unused', + line: 3, + column: 22, + endColumn: 38 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: '`update:foo` is defined as emit but never used.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'unused', + line: 4, + column: 19, + endColumn: 24 + } + ] + } + ] +}) diff --git a/tests/lib/rules/no-unused-properties.js b/tests/lib/rules/no-unused-properties.js index ecd41243e..b89ea01f0 100644 --- a/tests/lib/rules/no-unused-properties.js +++ b/tests/lib/rules/no-unused-properties.js @@ -4,12 +4,16 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const { RuleTester, Linter } = require('../../eslint-compat') +const assert = require('assert') const rule = require('../../../lib/rules/no-unused-properties') +const { + getTypeScriptFixtureTestOptions +} = require('../../test-utils/typescript') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2020, sourceType: 'module' } @@ -20,41 +24,70 @@ const allOptions = [ ] const deepDataOptions = [{ groups: ['data'], deepData: true }] +const unreferencedOptions = { + // Report errors when accessing via unknown property, e.g. this[varName] + unknownMemberAsUnreferenced: [ + { + groups: ['computed'], + unreferencedOptions: ['unknownMemberAsUnreferenced'] + } + ], + // Report errors when returning this + returnAsUnreferenced: [ + { + groups: ['computed'], + unreferencedOptions: ['returnAsUnreferenced'] + } + ], + // Report all + all: [ + { + groups: ['computed'], + unreferencedOptions: [ + 'unknownMemberAsUnreferenced', + 'returnAsUnreferenced' + ] + } + ] +} + tester.run('no-unused-properties', rule, { valid: [ - // a property used in a script expression + // vuex getters { filename: 'test.vue', code: ` - ` + + `, + options: allOptions }, - // default options { filename: 'test.vue', code: ` + ` }, { @@ -62,710 +95,703 @@ tester.run('no-unused-properties', rule, { code: ` + ` }, - - // a property being watched { filename: 'test.vue', code: ` + ` }, - - // a property used as a template identifier { filename: 'test.vue', code: ` - + ` }, - // properties used in a template expression + // vuex mutations { filename: 'test.vue', code: ` - + ` }, - - // a property used in v-if { filename: 'test.vue', code: ` - + ` }, - - // a property used in v-for { filename: 'test.vue', code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + ` }, - // a property used in v-html + // vuex actions { filename: 'test.vue', code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + ` }, - - // a property passed in a component { filename: 'test.vue', code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + ` }, - // a property used in v-on + // vuex state { filename: 'test.vue', code: ` - + ` }, - - // data used in a script expression { filename: 'test.vue', code: ` - `, - options: allOptions + + ` }, - - // data being watched { filename: 'test.vue', code: ` - `, - options: allOptions + + ` }, { filename: 'test.vue', code: ` + ` }, + + // pinia getters { filename: 'test.vue', code: ` + `, options: allOptions }, - - // data used as a template identifier { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - - // data used in a template expression { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - - // data used in v-if { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - - // data used in v-for { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - // data used in v-html + // pinia actions { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - - // data used in v-model { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - - // data passed in a component { filename: 'test.vue', code: ` + + ` + }, + { + filename: 'test.vue', + code: ` - `, - options: allOptions + + ` }, - // data used in v-on + // pinia state { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - - // computed property used in a script expression { filename: 'test.vue', code: ` - `, - options: allOptions + + ` }, - - // computed property being watched { filename: 'test.vue', code: ` - `, - options: allOptions + + ` }, - - // computed property used as a template identifier { filename: 'test.vue', code: ` - - `, - options: allOptions + + ` }, - // computed properties used in a template expression + // pinia writable state { filename: 'test.vue', code: ` + + ` + }, + { + filename: 'test.vue', + code: ` - `, - options: allOptions + + ` }, - - // computed property used in v-if { filename: 'test.vue', code: ` + + ` + }, + { + filename: 'test.vue', + code: ` - `, - options: allOptions + + ` }, - // computed property used in v-for + // a property used in a script expression { filename: 'test.vue', code: ` - - `, - options: allOptions + ` }, - - // computed property used in v-html { filename: 'test.vue', code: ` - - `, - options: allOptions + ` }, - - // computed property used in v-model + // default options { filename: 'test.vue', code: ` - - `, - options: allOptions + ` + }, + { + filename: 'test.vue', + code: ` + + ` }, - // computed property passed in a component + // a property being watched + { + filename: 'test.vue', + code: ` + + ` + }, + + // a property used as a template identifier { filename: 'test.vue', code: ` - `, - options: allOptions + ` }, - - // async data passed in a component + // a property used as a template $props member expression { filename: 'test.vue', code: ` - `, - options: allOptions + ` }, - - // ignores unused data when marked with eslint-disable + // a property used as a template $props expression { filename: 'test.vue', code: ` - `, - options: allOptions + ` }, - // trace this + // properties used in a template expression { filename: 'test.vue', code: ` + ` }, + // properties used in a template expression as $props member expression { filename: 'test.vue', code: ` + ` }, + + // a property used in v-if { filename: 'test.vue', code: ` + ` }, - // use rest + // a property used in v-if as $props member expression { filename: 'test.vue', code: ` ` }, + // a property used in v-for as $props member expression { filename: 'test.vue', code: ` + ` }, - // render & functional + // a property used in v-html { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('smart-list', { - functional: true, - props: { - items: { - type: Array, - required: true - }, - isOrdered: Boolean - }, - render: function (createElement, context) { - function appropriateListComponent () { - var items = context.props.items - - if (items.length === 0) return EmptyList - if (typeof items[0] === 'object') return TableList - if (context.props.isOrdered) return OrderedList - - return UnorderedList - } - - return createElement( - appropriateListComponent(), - context.data, - context.children - ) - } - }) + + ` }, + // a property used in v-html as $props member expression { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, {props}) { - return createElement('button', props.foo) - } - }) + + ` }, + + // a property passed in a component { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, ctx) { - return createElement('button', fn(ctx.props)) + + + ` + }, + // a property passed in a component as $props member expression + { + filename: 'test.vue', + code: ` + + + ` + }, + + // a property used in v-on + { + filename: 'test.vue', + code: ` + + + ` + }, + // a property used in v-on as $props member expression + { + filename: 'test.vue', + code: ` + + + ` + }, + // a property used in v-on as $props expression + { + filename: 'test.vue', + code: ` + + + ` + }, + + // data used in a script expression + { + filename: 'test.vue', + code: ` + + `, + options: allOptions + }, + + // data being watched + { + filename: 'test.vue', + code: ` + + `, + options: allOptions + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + options: allOptions + }, + + // data used as a template identifier + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data used in a template expression + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data used in v-if + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data used in v-for + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data used in v-html + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data used in v-model + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data passed in a component + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // data used in v-on + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed property used in a script expression + { + filename: 'test.vue', + code: ` + + `, + options: allOptions + }, + + // computed property being watched + { + filename: 'test.vue', + code: ` + + `, + options: allOptions + }, + + // computed property used as a template identifier + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed properties used in a template expression + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed property used in v-if + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed property used in v-for + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed property used in v-html + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed property used in v-model + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // computed property passed in a component + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // async data passed in a component + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // ignores unused data when marked with eslint-disable + { + filename: 'test.vue', + code: ` + + + `, + options: allOptions + }, + + // trace this + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + // use rest + { + filename: 'test.vue', + code: ` + + + ` + }, + + // function trace + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + + // render & functional + { + filename: 'test.js', + code: ` + Vue.component('smart-list', { + functional: true, + props: { + items: { + type: Array, + required: true + }, + isOrdered: Boolean + }, + render: function (createElement, context) { + function appropriateListComponent () { + var items = context.props.items + + if (items.length === 0) return EmptyList + if (typeof items[0] === 'object') return TableList + if (context.props.isOrdered) return OrderedList + + return UnorderedList + } + + return createElement( + appropriateListComponent(), + context.data, + context.children + ) + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, {props}) { + return createElement('button', props.foo) + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, ctx) { + return createElement('button', fn(ctx.props)) + } + }) + + function fn(props) { + return props.foo + } + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, ctx) { + return createElement('button', fn(ctx)) + } + }) + + function fn({props}) { + return props.foo + } + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, {props:{foo}}) { + return createElement('button', foo) + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, {props:[bar]}) { + return createElement('button') + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, {props:bar={}}) { + return createElement('button', bar.foo) + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo'], + render: function (createElement, {...foo}) { + return createElement('button') + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo', 'bar'], + render: function (createElement, ctx) { + const a = ctx.props + const b = ctx.props + return createElement('button', a.foo + b.bar) + } + }) + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo', 'bar'], + render: function (createElement, {props: a, props: b}) { + return createElement('button', a.foo + b.bar) + } + }) + ` + }, + // render for Vue 3.x + { + filename: 'test.vue', + code: ` + export default { + props: ['foo'], + render (props) { + return h('button', props.foo) + } + }) + ` + }, + { + filename: 'test.vue', + code: ` + export default { + props: ['foo'], + render ({foo}) { + return h('button', foo) + } + }) + ` + }, + { + filename: 'test.vue', + code: ` + export default { + props: ['foo'], + render (bar) { + const {...baz} = bar + return h('button') + } + }) + ` + }, + // Vue.js 3.x Template Refs + { + filename: 'test.vue', + code: ` + + + `, + options: [{ groups: ['props', 'setup'] }] + }, + + // sparse array + { + filename: 'test.vue', + code: ` + + + ` + }, + // optional chaining + { + filename: 'test.vue', + code: ` + ` + }, + { + filename: 'test.js', + code: ` + Vue.component('MyButton', { + functional: true, + props: ['foo', 'bar'], + render: function (createElement, ctx) { + const a = ctx + const b = a?.props?.foo + const c = (a?.props)?.bar + } + }) + ` + }, + // handlers + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['props', 'methods'] }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['props', 'data'] }] + }, + // contexts + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + + // deep data + { + filename: 'test.vue', + code: ` + + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + `, + options: deepDataOptions + }, + { + filename: 'test.vue', + code: ` + + `, + options: deepDataOptions + }, + + // ignore public members + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['data'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['computed'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['methods'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [{ groups: ['setup'], ignorePublicMembers: true }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['props', 'data', 'computed', 'methods', 'setup'], + ignorePublicMembers: true + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['props', 'computed'], + ignorePublicMembers: true + } + ] + }, + + // expose + { + filename: 'test.vue', + code: ` + + `, + options: allOptions + }, + + //style vars + { + filename: 'test.vue', + code: ` + + + + + + ` + }, + + // toRefs + { + // https://github.com/vuejs/eslint-plugin-vue/issues/1643 + filename: 'test.vue', + code: ` + + + + + `, + languageOptions: { + parserOptions: { + parser: '@typescript-eslint/parser' + } } - ` }, + + // Vue2 functional component { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, ctx) { - return createElement('button', fn(ctx)) - } - }) + - function fn({props}) { - return props.foo - } + ` + }, + { + // defineModel + filename: 'test.vue', + code: ` + + ` }, + + // props.prop in template { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, {props:{foo}}) { - return createElement('button', foo) + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + code: ` + + `, + ...getTypeScriptFixtureTestOptions() + } + ], + invalid: [ + // vuex unused mutations + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 } - }) - ` + ] }, { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, {props:[bar]}) { - return createElement('button') + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 } - }) - ` + ] }, { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, {props:bar={}}) { - return createElement('button', bar.foo) + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 4 } - }) - ` + ] }, { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo'], - render: function (createElement, {...foo}) { - return createElement('button') + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 } - }) - ` + ] }, + + // vuex unused actions { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo', 'bar'], - render: function (createElement, ctx) { - const a = ctx.props - const b = ctx.props - return createElement('button', a.foo + b.bar) + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 } - }) - ` + ] }, { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo', 'bar'], - render: function (createElement, {props: a, props: b}) { - return createElement('button', a.foo + b.bar) + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 4 } - }) - ` + ] }, - // render for Vue 3.x { filename: 'test.vue', code: ` - export default { - props: ['foo'], - render (props) { - return h('button', props.foo) + + + `, + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 } - }) - ` + ] }, + + // vuex unused state { filename: 'test.vue', code: ` - export default { - props: ['foo'], - render ({foo}) { - return h('button', foo) + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 } - }) - ` + ] }, { filename: 'test.vue', code: ` - export default { - props: ['foo'], - render (bar) { - const {...baz} = bar - return h('button') + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 } - }) - ` + ] }, - // Vue.js 3.x Template Refs { filename: 'test.vue', code: ` - - - + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 } - `, - options: [{ groups: ['props', 'setup'] }] + ] }, - - // sparse array { filename: 'test.vue', code: ` - - ` + + `, + errors: [ + { + message: "'count' of computed property found, but never used.", + line: 5 + } + ] }, - // optional chaining { filename: 'test.vue', code: ` - + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 } - - function fn(a) { - return a?.foo + a?.bar - } - ` + ] }, + + // vuex unused getters { - filename: 'test.js', + filename: 'test.vue', code: ` - Vue.component('MyButton', { - functional: true, - props: ['foo', 'bar'], - render: function (createElement, ctx) { - const a = ctx - const b = a?.props?.foo - const c = (a?.props)?.bar + + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 } - }) - ` + ] }, - // handlers { filename: 'test.vue', code: ` - + + `, - options: [{ groups: ['props', 'methods'] }] + errors: [ + { + message: "'count' of computed property found, but never used.", + line: 4 + } + ] }, { filename: 'test.vue', code: ` - + + `, - options: [{ groups: ['props', 'data'] }] + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] }, - // contexts { filename: 'test.vue', code: ` - - ` + + + `, + errors: [ + { + message: "'count3' of computed property found, but never used.", + line: 7 + } + ] }, { filename: 'test.vue', code: ` - - ` + + + `, + errors: [ + { + message: "'a' of computed property found, but never used.", + line: 6 + } + ] }, { filename: 'test.vue', code: ` - - ` + + + `, + errors: [ + { + message: "'a' of computed property found, but never used.", + line: 6 + } + ] }, - // deep data + // pinia unused actions { filename: 'test.vue', code: ` - + `, - options: deepDataOptions + errors: [ + { + message: "'add2' of method found, but never used.", + line: 6 + } + ] }, { filename: 'test.vue', code: ` + `, - options: deepDataOptions + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` + `, - options: deepDataOptions + errors: [ + { + message: "'add2' of method found, but never used.", + line: 4 + } + ] }, { filename: 'test.vue', code: ` - + `, - options: deepDataOptions + errors: [ + { + message: "'add2' of method found, but never used.", + line: 5 + } + ] }, + + // pinia unused state { filename: 'test.vue', code: ` + `, - options: deepDataOptions + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` - - + `, - options: deepDataOptions + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 + } + ] }, { filename: 'test.vue', code: ` - - + `, - options: deepDataOptions + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` + - + `, + errors: [ + { + message: "'count' of computed property found, but never used.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + `, - options: deepDataOptions + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 + } + ] }, + + // pinia unused writable state { filename: 'test.vue', code: ` + `, - options: deepDataOptions + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` - - + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 } - } - `, - options: deepDataOptions + ] }, - - // ignore public members { filename: 'test.vue', code: ` + `, - options: [{ groups: ['data'], ignorePublicMembers: true }] + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` + `, - options: [{ groups: ['computed'], ignorePublicMembers: true }] + errors: [ + { + message: "'count' of computed property found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` + `, - options: [{ groups: ['methods'], ignorePublicMembers: true }] + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 6 + } + ] }, + + // pinia unused getters { filename: 'test.vue', code: ` + `, - options: [{ groups: ['setup'], ignorePublicMembers: true }] + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 + } + ] }, { filename: 'test.vue', code: ` - - `, - options: [ + + + `, + errors: [ { - groups: ['props', 'data', 'computed', 'methods', 'setup'], - ignorePublicMembers: true + message: "'count' of computed property found, but never used.", + line: 4 } ] }, - - // expose { filename: 'test.vue', code: ` - - + + `, + errors: [ + { + message: "'count2' of computed property found, but never used.", + line: 5 } - } - `, - options: allOptions + ] }, - - //style vars { filename: 'test.vue', code: ` + - + `, + errors: [ + { + message: "'count3' of computed property found, but never used.", + line: 7 + } + ] + }, + { + filename: 'test.vue', + code: ` - - - ` - }, - - // toRefs + + `, + errors: [ + { + message: "'a' of computed property found, but never used.", + line: 6 + } + ] + }, { - // https://github.com/vuejs/eslint-plugin-vue/issues/1643 filename: 'test.vue', - parserOptions: { - parser: '@typescript-eslint/parser' - }, code: ` - - - + + `, + errors: [ { - currentPage: 1, - totalRows: 100, - rowsPerPage: 10, - } - ); - const { currentPage, totalRows, rowsPerPage } = toRefs(props); - - const totalPages = computed(() => - Math.ceil(totalRows.value / rowsPerPage.value) - ); - - const pages: ComputedRef<(number | '...')[]> = computed(() => - getPagesOnPagination(currentPage.value, totalPages.value) - ); - - const emit = defineEmits<{ - (e: 'changePage', page: number): void; - }>(); - - function changePage(page: number | '...') { - if (page === '...') { - return; + message: "'a' of computed property found, but never used.", + line: 6 } - emit('changePage', page); - } - - - ` - } - ], + ] + }, - invalid: [ // unused property { filename: 'test.vue', @@ -2756,6 +4489,298 @@ tester.run('no-unused-properties', rule, { "'foo.bar.b' of data found, but never used.", "'foo.baz' of data found, but never used." ] + }, + + // Vue2 functional component + { + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'a' of property found, but never used.", + line: 9 + }, + { + message: "'b' of property found, but never used.", + line: 10 + } + ] + }, + + // unreferencedOptions: unknownMemberAsUnreferenced + { + filename: 'test.vue', + code: ` + `, + options: unreferencedOptions.unknownMemberAsUnreferenced, + errors: [ + { + message: "'two' of computed property found, but never used.", + line: 8 + } + ] + }, + // unreferencedOptions: returnAsUnreferenced + { + filename: 'test.vue', + code: ` + `, + options: unreferencedOptions.returnAsUnreferenced, + errors: [ + { + message: "'two' of computed property found, but never used.", + line: 8 + } + ] + }, + // unreferencedOptions: returnAsUnreferenced via variable with deepData + { + filename: 'test.vue', + code: ` + + `, + options: [ + { + groups: ['data'], + unreferencedOptions: ['returnAsUnreferenced'], + deepData: true + } + ], + errors: [ + { + message: "'foo.bar' of data found, but never used.", + line: 7 + } + ] + }, + // unreferencedOptions: all + { + filename: 'test.vue', + code: ` + `, + options: unreferencedOptions.all, + errors: [ + { + message: "'two' of computed property found, but never used.", + line: 8 + } + ] + }, + // script setup with typescript + { + code: ` + + `, + errors: [ + { + message: "'baz' of property found, but never used.", + line: 4 + } + ], + ...getTypeScriptFixtureTestOptions() + }, + + { + // defineModel + filename: 'test.vue', + code: ` + + + `, + errors: [ + { + message: "'unused' of property found, but never used.", + line: 6 + } + ] + }, + + // a property used as a template $props member expression + { + filename: 'test.vue', + code: ` + + + `, + errors: ["'bar' of property found, but never used."] + }, + + // props.prop in template + { + filename: 'test.vue', + code: ` + + `, + errors: ["'b' of property found, but never used."] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: ["'b' of property found, but never used."] + }, + { + code: ` + + `, + errors: ["'baz' of property found, but never used."], + ...getTypeScriptFixtureTestOptions() } ] }) + +// https://github.com/vuejs/eslint-plugin-vue/issues/1789 +describe('`vue/no-unused-properties` and `vue/no-unused-components` should not conflict.', () => { + const linter = new Linter() + const config = { + files: ['**/*.vue'], + plugins: { + vue: { + rules: { + 'no-unused-components': require('../../../lib/rules/no-unused-components'), + 'no-unused-properties': rule + } + } + }, + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + }, + rules: { + 'vue/no-unused-components': 'error', + 'vue/no-unused-properties': 'error' + } + } + + it('should not be a false positive when using CSS v-bind().', () => { + const code = ` + + + ` + assert.deepStrictEqual(linter.verify(code, config, 'test.vue'), []) + }) +}) diff --git a/tests/lib/rules/no-unused-refs.js b/tests/lib/rules/no-unused-refs.js index 2547ef05d..a4407a608 100644 --- a/tests/lib/rules/no-unused-refs.js +++ b/tests/lib/rules/no-unused-refs.js @@ -4,12 +4,12 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-unused-refs') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2020, sourceType: 'module' } @@ -315,6 +315,20 @@ tester.run('no-unused-refs', rule, { const x = ref(null) ` + }, + { + filename: 'test.vue', + code: ` + + + ` } ], diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js index f57733d3f..60e1c276b 100644 --- a/tests/lib/rules/no-unused-vars.js +++ b/tests/lib/rules/no-unused-vars.js @@ -5,54 +5,25 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-unused-vars') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('no-unused-vars', rule, { valid: [ - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, - { - code: '' - }, + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', { code: '', options: [{ ignorePattern: '^_' }] @@ -116,19 +87,19 @@ tester.run('no-unused-vars', rule, { }, { code: '', + options: [{ ignorePattern: '^_' }], errors: [ { message: "'x' is defined but never used.", suggestions: [ { - desc: 'Replace the x with _x', + desc: 'Replace `x` with `_x` to ignore the unused variable.', output: '' } ] } - ], - options: [{ ignorePattern: '^_' }] + ] }, { code: '', @@ -137,13 +108,13 @@ tester.run('no-unused-vars', rule, { }, { code: '', - errors: ["'props' is defined but never used."], - options: [{ ignorePattern: '^ignore' }] + options: [{ ignorePattern: '^ignore' }], + errors: ["'props' is defined but never used."] }, { code: '', - errors: ["'props' is defined but never used."], - options: [{ ignorePattern: '^ignore' }] + options: [{ ignorePattern: '^ignore' }], + errors: ["'props' is defined but never used."] }, { code: '', @@ -152,7 +123,18 @@ tester.run('no-unused-vars', rule, { { code: '', options: [{ ignorePattern: '^_' }], - errors: ["'a' is defined but never used."] + errors: [ + { + message: "'a' is defined but never used.", + suggestions: [ + { + messageId: 'replaceWithUnderscore', + output: + '' + } + ] + } + ] }, { code: '', diff --git a/tests/lib/rules/no-use-computed-property-like-method.js b/tests/lib/rules/no-use-computed-property-like-method.js index 539e6275a..4a5e25ebe 100644 --- a/tests/lib/rules/no-use-computed-property-like-method.js +++ b/tests/lib/rules/no-use-computed-property-like-method.js @@ -3,20 +3,15 @@ * See LICENSE file in root directory for full license. */ -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-use-computed-property-like-method') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020, sourceType: 'module' } + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } }) tester.run('no-use-computed-property-like-method', rule, { @@ -569,6 +564,78 @@ tester.run('no-use-computed-property-like-method', rule, { } ` + }, + { + // expression may be a function: https://github.com/vuejs/eslint-plugin-vue/issues/2037 + filename: 'test.vue', + code: ` + + ` + }, + { + // expression may be a function: https://github.com/vuejs/eslint-plugin-vue/issues/2037 + filename: 'test.vue', + code: ` + + ` } ], invalid: [ @@ -1101,6 +1168,42 @@ tester.run('no-use-computed-property-like-method', rule, { `, errors: ['Use x instead of x().'] + }, + { + // expression may be a function: https://github.com/vuejs/eslint-plugin-vue/issues/2037 + filename: 'test.vue', + code: ` + + `, + errors: [ + 'Use this.computedReturnNotFunction1 instead of this.computedReturnNotFunction1().', + 'Use this.computedReturnNotFunction2 instead of this.computedReturnNotFunction2().' + ] } ] }) diff --git a/tests/lib/rules/no-use-v-else-with-v-for.js b/tests/lib/rules/no-use-v-else-with-v-for.js new file mode 100644 index 000000000..896ac122d --- /dev/null +++ b/tests/lib/rules/no-use-v-else-with-v-for.js @@ -0,0 +1,97 @@ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/no-use-v-else-with-v-for') + +const tester = new RuleTester({ + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } +}) + +tester.run('no-use-v-else-with-v-for', rule, { + valid: [ + { + // caught by `vue/no-use-v-if-with-v-for` + filename: 'test.vue', + code: ` + + ` + }, + { + // `v-if`/`v-else-if`/`v-else` only + filename: 'test.vue', + code: ` + + ` + }, + { + // `v-for` only + filename: 'test.vue', + code: ` + + ` + }, + { + // `v-else-if`/`v-else` in template + `v-for` + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + 'Unexpected `v-else` and `v-for` on the same element. Move `v-else` to a wrapper element instead.', + line: 4, + endLine: 4, + column: 11, + endColumn: 52 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + 'Unexpected `v-else-if` and `v-for` on the same element. Move `v-else-if` to a wrapper element instead.', + line: 4, + endLine: 4, + column: 11, + endColumn: 61 + } + ] + } + ] +}) diff --git a/tests/lib/rules/no-use-v-if-with-v-for.js b/tests/lib/rules/no-use-v-if-with-v-for.js index 4ed5e6f46..6d2661a4c 100644 --- a/tests/lib/rules/no-use-v-if-with-v-for.js +++ b/tests/lib/rules/no-use-v-if-with-v-for.js @@ -3,20 +3,11 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-use-v-if-with-v-for') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('no-use-v-if-with-v-for', rule, { diff --git a/tests/lib/rules/no-useless-concat.js b/tests/lib/rules/no-useless-concat.js index 28ac8240d..eca6901bb 100644 --- a/tests/lib/rules/no-useless-concat.js +++ b/tests/lib/rules/no-useless-concat.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-useless-concat') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('no-useless-concat', rule, { diff --git a/tests/lib/rules/no-useless-mustaches.js b/tests/lib/rules/no-useless-mustaches.js index e12d33c1e..77d055e17 100644 --- a/tests/lib/rules/no-useless-mustaches.js +++ b/tests/lib/rules/no-useless-mustaches.js @@ -3,20 +3,12 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/no-useless-mustaches.js') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2020, sourceType: 'module' } @@ -194,6 +186,54 @@ tester.run('no-useless-mustaches', rule, { 'Unexpected mustache interpolation with a string literal value.' ] }, + { + code: ` + + `, + output: ` + + `, + errors: [ + 'Unexpected mustache interpolation with a string literal value.', + 'Unexpected mustache interpolation with a string literal value.', + 'Unexpected mustache interpolation with a string literal value.', + 'Unexpected mustache interpolation with a string literal value.' + ] + }, + { + code: ` + + `, + output: ` + + `, + errors: [ + 'Unexpected mustache interpolation with a string literal value.', + 'Unexpected mustache interpolation with a string literal value.', + 'Unexpected mustache interpolation with a string literal value.', + 'Unexpected mustache interpolation with a string literal value.' + ] + }, { code: ` `, - options: [ - 'never', - { - objectsInObjects: true - } - ], output: ` `, + options: [ + 'never', + { + objectsInObjects: true + } + ], errors: [ "There should be no space after '{'.", "There should be no space after '{'.", @@ -145,13 +144,13 @@ tester.run('object-curly-spacing', rule, { Hello World `, - options: ['never'], output: ` `, + options: ['never'], errors: [ "There should be no space after '{'.", "There should be no space after '{'.", diff --git a/tests/lib/rules/object-property-newline.js b/tests/lib/rules/object-property-newline.js index 2de002caf..31e37dfb1 100644 --- a/tests/lib/rules/object-property-newline.js +++ b/tests/lib/rules/object-property-newline.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/object-property-newline') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2020 } }) tester.run('object-property-newline', rule, { diff --git a/tests/lib/rules/object-shorthand.js b/tests/lib/rules/object-shorthand.js new file mode 100644 index 000000000..f216048f0 --- /dev/null +++ b/tests/lib/rules/object-shorthand.js @@ -0,0 +1,81 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/object-shorthand') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('object-shorthand', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected property shorthand.', + line: 3, + column: 23 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: ['never'], + errors: [ + { + message: 'Expected longform property syntax.', + line: 3, + column: 23 + } + ] + } + ] +}) diff --git a/tests/lib/rules/one-component-per-file.js b/tests/lib/rules/one-component-per-file.js index 5cdabfa85..7c62e69a9 100644 --- a/tests/lib/rules/one-component-per-file.js +++ b/tests/lib/rules/one-component-per-file.js @@ -4,15 +4,11 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - const rule = require('../../../lib/rules/one-component-per-file') -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const ruleTester = new RuleTester({ - parserOptions: { + languageOptions: { ecmaVersion: 2018, sourceType: 'module' } diff --git a/tests/lib/rules/operator-linebreak.js b/tests/lib/rules/operator-linebreak.js index 8b0c731e6..68843a7ef 100644 --- a/tests/lib/rules/operator-linebreak.js +++ b/tests/lib/rules/operator-linebreak.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/operator-linebreak') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2020 } }) tester.run('operator-linebreak', rule, { diff --git a/tests/lib/rules/order-in-components.js b/tests/lib/rules/order-in-components.js index 76d510964..c8afe3db1 100644 --- a/tests/lib/rules/order-in-components.js +++ b/tests/lib/rules/order-in-components.js @@ -5,14 +5,13 @@ 'use strict' const rule = require('../../../lib/rules/order-in-components') -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester -const ruleTester = new RuleTester() - -const parserOptions = { +const languageOptions = { ecmaVersion: 2020, sourceType: 'module' } +const ruleTester = new RuleTester({ languageOptions }) ruleTester.run('order-in-components', rule, { valid: [ @@ -32,7 +31,7 @@ ruleTester.run('order-in-components', rule, { }, } `, - parserOptions + languageOptions }, { filename: 'example.vue', @@ -51,6 +50,8 @@ ruleTester.run('order-in-components', rule, { model, props, propsData, emits, + slots, + expose, setup, data, computed, @@ -75,21 +76,21 @@ ruleTester.run('order-in-components', rule, { renderError, }; `, - parserOptions + languageOptions }, { filename: 'test.vue', code: ` export default {} `, - parserOptions + languageOptions }, { filename: 'test.vue', code: ` export default 'example-text' `, - parserOptions + languageOptions }, { filename: 'test.jsx', @@ -103,7 +104,24 @@ ruleTester.run('order-in-components', rule, { }, } `, - parserOptions + languageOptions + }, + { + filename: 'test.vue', + code: ` + export default { + name: 'app', + data () { + return { + msg: 'Welcome to Your Vue.js App' + } + }, + computed: { + ...mapStates(['foo']) + }, + } + `, + languageOptions }, { filename: 'test.js', @@ -118,14 +136,14 @@ ruleTester.run('order-in-components', rule, { } }) `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', code: ` Vue.component('example') `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', @@ -141,7 +159,7 @@ ruleTester.run('order-in-components', rule, { } }) `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', @@ -157,14 +175,26 @@ ruleTester.run('order-in-components', rule, { } }) `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', code: ` new Vue() `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } + }, + { + filename: 'example.vue', + code: ` + + `, + languageOptions: { parser: require('vue-eslint-parser') } } ], @@ -184,7 +214,6 @@ ruleTester.run('order-in-components', rule, { }, } `, - parserOptions, output: ` export default { name: 'app', @@ -198,6 +227,7 @@ ruleTester.run('order-in-components', rule, { }, } `, + languageOptions, errors: [ { message: @@ -206,6 +236,84 @@ ruleTester.run('order-in-components', rule, { } ] }, + { + filename: 'test.vue', + code: ` + import { defineComponent } from 'vue' + export default defineComponent({ + name: 'app', + data () { + return { + msg: 'Welcome to Your Vue.js App' + } + }, + props: { + propA: Number, + }, + }) + `, + output: ` + import { defineComponent } from 'vue' + export default defineComponent({ + name: 'app', + props: { + propA: Number, + }, + data () { + return { + msg: 'Welcome to Your Vue.js App' + } + }, + }) + `, + languageOptions, + errors: [ + { + message: + 'The "props" property should be above the "data" property on line 5.', + line: 10 + } + ] + }, + { + filename: 'test.vue', + code: ` + import { defineNuxtComponent } from '#app' + export default defineNuxtComponent({ + name: 'app', + data () { + return { + msg: 'Welcome to Your Vue.js App' + } + }, + props: { + propA: Number, + }, + }) + `, + output: ` + import { defineNuxtComponent } from '#app' + export default defineNuxtComponent({ + name: 'app', + props: { + propA: Number, + }, + data () { + return { + msg: 'Welcome to Your Vue.js App' + } + }, + }) + `, + languageOptions, + errors: [ + { + message: + 'The "props" property should be above the "data" property on line 5.', + line: 10 + } + ] + }, { filename: 'test.jsx', code: ` @@ -226,11 +334,6 @@ ruleTester.run('order-in-components', rule, { }, } `, - parserOptions: { - ecmaVersion: 6, - sourceType: 'module', - ecmaFeatures: { jsx: true } - }, output: ` export default { name: 'app', @@ -249,6 +352,13 @@ ruleTester.run('order-in-components', rule, { }, } `, + languageOptions: { + ecmaVersion: 6, + sourceType: 'module', + parserOptions: { + ecmaFeatures: { jsx: true } + } + }, errors: [ { message: @@ -281,7 +391,6 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, output: ` Vue.component('smart-list', { name: 'app', @@ -294,6 +403,7 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -316,7 +426,6 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, output: ` app.component('smart-list', { name: 'app', @@ -329,6 +438,7 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -352,7 +462,6 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, output: ` const { component } = Vue; component('smart-list', { @@ -366,6 +475,7 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -389,7 +499,6 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, output: ` new Vue({ el: '#app', @@ -403,6 +512,7 @@ ruleTester.run('order-in-components', rule, { template: '
' }) `, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -436,7 +546,6 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: ` export default { name: 'burger', @@ -455,6 +564,7 @@ ruleTester.run('order-in-components', rule, { }, }; `, + languageOptions, errors: [ { message: @@ -473,7 +583,6 @@ ruleTester.run('order-in-components', rule, { test: 'ok' }; `, - parserOptions, output: ` export default { data() { @@ -483,6 +592,7 @@ ruleTester.run('order-in-components', rule, { }; `, options: [{ order: ['data', 'test', 'name'] }], + languageOptions, errors: [ { message: @@ -502,7 +612,6 @@ ruleTester.run('order-in-components', rule, { name: 'burger' }; `, - parserOptions, output: ` export default { /** name of vue component */ @@ -512,6 +621,7 @@ ruleTester.run('order-in-components', rule, { } }; `, + languageOptions, errors: [ { message: @@ -531,7 +641,6 @@ ruleTester.run('order-in-components', rule, { name: 'burger' }; `, - parserOptions, output: ` export default { /** name of vue component */ @@ -541,6 +650,7 @@ ruleTester.run('order-in-components', rule, { }/*test*/ }; `, + languageOptions, errors: [ { message: @@ -552,8 +662,8 @@ ruleTester.run('order-in-components', rule, { { filename: 'example.vue', code: `export default {data(){},name:'burger'};`, - parserOptions, output: `export default {name:'burger',data(){}};`, + languageOptions, errors: [ { message: @@ -573,13 +683,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: obj.fn(), + }; + ` + } + ] } ] }, @@ -594,13 +717,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: new MyClass(), + }; + ` + } + ] } ] }, @@ -615,13 +751,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: i++, + }; + ` + } + ] } ] }, @@ -636,13 +785,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: i = 0, + }; + ` + } + ] } ] }, @@ -657,13 +819,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: template\`\${foo}\`, + }; + ` + } + ] } ] }, @@ -678,13 +853,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + [obj.fn()]: 'test', + }; + ` + } + ] } ] }, @@ -699,13 +887,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: {test: obj.fn()}, + }; + ` + } + ] } ] }, @@ -720,13 +921,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: [obj.fn(), 1], + }; + ` + } + ] } ] }, @@ -741,13 +955,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: obj.fn().prop, + }; + ` + } + ] } ] }, @@ -762,13 +989,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: delete obj.prop, + }; + ` + } + ] } ] }, @@ -783,13 +1023,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: fn() + a + b, + }; + ` + } + ] } ] }, @@ -804,13 +1057,26 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: a ? fn() : null, + }; + ` + } + ] } ] }, @@ -825,13 +1091,62 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: null, + languageOptions, errors: [ { message: 'The "name" property should be above the "data" property on line 3.', - line: 6 + line: 6, + suggestions: [ + { + desc: 'Manually move "name" property above "data" property on line 3 (might break side effects).', + output: ` + export default { + name: 'burger', + data() { + }, + test: \`test \${fn()} \${a}\`, + }; + ` + } + ] + } + ] + }, + { + // side-effects https://github.com/vuejs/eslint-plugin-vue/issues/2418 + filename: 'example.vue', + code: ` + export default { + computed: { + ...mapStates(['foo']) + }, + data() { + }, + }; + `, + output: null, + languageOptions, + errors: [ + { + message: + 'The "data" property should be above the "computed" property on line 3.', + line: 6, + suggestions: [ + { + desc: 'Manually move "data" property above "computed" property on line 3 (might break side effects).', + output: ` + export default { + data() { + }, + computed: { + ...mapStates(['foo']) + }, + }; + ` + } + ] } ] }, @@ -846,7 +1161,6 @@ ruleTester.run('order-in-components', rule, { test: fn(), }; `, - parserOptions, output: ` export default { name: 'burger', @@ -855,6 +1169,7 @@ ruleTester.run('order-in-components', rule, { test: fn(), }; `, + languageOptions, errors: [ { message: @@ -883,7 +1198,6 @@ ruleTester.run('order-in-components', rule, { name: 'burger', }; `, - parserOptions, output: ` export default { name: 'burger', @@ -901,6 +1215,7 @@ ruleTester.run('order-in-components', rule, { testOptionalChaining: a?.b?.c, }; `, + languageOptions, errors: [ { message: @@ -908,6 +1223,125 @@ ruleTester.run('order-in-components', rule, { line: 15 } ] + }, + { + filename: 'example.vue', + code: ` + + `, + output: ` + + `, + languageOptions: { + parser: require('vue-eslint-parser'), + ...languageOptions, + parserOptions: { + parser: { ts: require.resolve('@typescript-eslint/parser') } + } + }, + errors: [ + { + message: + 'The "props" property should be above the "setup" property on line 4.', + line: 5 + } + ] + }, + { + filename: 'example.vue', + code: ` + + `, + output: ` + + `, + languageOptions: { parser: require('vue-eslint-parser') }, + errors: [ + { + message: + 'The "name" property should be above the "inheritAttrs" property on line 4.', + line: 5 + } + ] + }, + { + filename: 'example.vue', + code: ` + export default { + setup, + slots, + expose, + }; + `, + output: ` + export default { + slots, + setup, + expose, + }; + `, + languageOptions, + errors: [ + { + message: + 'The "slots" property should be above the "setup" property on line 3.', + line: 4 + }, + { + message: + 'The "expose" property should be above the "setup" property on line 3.', + line: 5 + } + ] + }, + { + filename: 'example.vue', + code: ` + export default { + slots, + setup, + expose, + }; + `, + output: ` + export default { + slots, + expose, + setup, + }; + `, + languageOptions, + errors: [ + { + message: + 'The "expose" property should be above the "setup" property on line 4.', + line: 5 + } + ] } ] }) diff --git a/tests/lib/rules/padding-line-between-blocks.js b/tests/lib/rules/padding-line-between-blocks.js index 622839bdc..e78422e87 100644 --- a/tests/lib/rules/padding-line-between-blocks.js +++ b/tests/lib/rules/padding-line-between-blocks.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/padding-line-between-blocks') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2020 } }) tester.run('padding-line-between-blocks', rule, { @@ -125,12 +124,12 @@ tester.run('padding-line-between-blocks', rule, { `, - options: ['never'], output: ` `, + options: ['never'], errors: [ { message: 'Unexpected blank line before this block.', @@ -214,7 +213,6 @@ tester.run('padding-line-between-blocks', rule, { `, - options: ['never'], output: ` @@ -225,6 +223,7 @@ tester.run('padding-line-between-blocks', rule, { `, + options: ['never'], errors: [ { message: 'Unexpected blank line before this block.', @@ -290,7 +289,6 @@ tester.run('padding-line-between-blocks', rule, { `, - options: ['never'], output: ` @@ -304,6 +302,7 @@ tester.run('padding-line-between-blocks', rule, { TEXT `, + options: ['never'], errors: [ { message: 'Unexpected blank line before this block.', diff --git a/tests/lib/rules/padding-line-between-tags.js b/tests/lib/rules/padding-line-between-tags.js new file mode 100644 index 000000000..2bb656201 --- /dev/null +++ b/tests/lib/rules/padding-line-between-tags.js @@ -0,0 +1,1301 @@ +/** + * @author dev1437 + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/padding-line-between-tags') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('padding-line-between-tags', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + `, + options: [ + [ + { blankLine: 'consistent', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'br' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: '*', next: 'br' }, + { blankLine: 'never', prev: 'br', next: '*' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'div' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'br', next: 'div' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'always', prev: 'br', next: 'img' }]] + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + options: [ + [ + { blankLine: 'never', prev: 'br', next: 'img' }, + { blankLine: 'always', prev: 'br', next: 'img' } + ] + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]] + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 5, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 13 + }, + { + message: 'Expected blank line before this tag.', + line: 9, + column: 13 + }, + { + message: 'Expected blank line before this tag.', + line: 10, + column: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 6, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 6, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 10, + column: 15 + }, + { + message: 'Expected blank line before this tag.', + line: 15, + column: 9 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: '*' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: '*', next: 'br' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 8, + column: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 9, + column: 13 + }, + { + message: 'Unexpected blank line before this tag.', + line: 11, + column: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 6, + column: 11 + }, + { + message: 'Unexpected blank line before this tag.', + line: 9, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'never', prev: '*', next: '*' }, + { blankLine: 'always', prev: 'br', next: 'div' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 8, + column: 13 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'div' }, + { blankLine: 'never', prev: 'br', next: 'img' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 5, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 9, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'always', prev: 'br', next: 'div' }, + { blankLine: 'always', prev: 'div', next: 'br' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 8, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'always', prev: 'br', next: 'div' }, + { blankLine: 'always', prev: 'br', next: 'br' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 9, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'always', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'br' } + ] + ], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 5, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 8, + column: 11 + }, + { + message: 'Unexpected blank line before this tag.', + line: 10, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: 'br', next: 'br' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 11, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'always', prev: '*', next: 'br' }]], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 4, + column: 18 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + errors: [ + { + message: 'Expected blank line before this tag.', + line: 4, + column: 23 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 6, + column: 12 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 7, + column: 12 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 8, + column: 12 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'never', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 10, + column: 12 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]], + errors: [ + { + message: 'Expected blank line before this tag.', + line: 7, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 8, + column: 11 + }, + { + message: 'Expected blank line before this tag.', + line: 9, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [ + [ + { blankLine: 'consistent', prev: '*', next: '*' }, + { blankLine: 'never', prev: 'br', next: 'br' } + ] + ], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 7, + column: 11 + }, + { + message: 'Unexpected blank line before this tag.', + line: 10, + column: 11 + }, + { + message: 'Unexpected blank line before this tag.', + line: 13, + column: 11 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [[{ blankLine: 'consistent', prev: '*', next: '*' }]], + errors: [ + { + message: 'Unexpected blank line before this tag.', + line: 7, + column: 11 + } + ] + } + ] +}) diff --git a/tests/lib/rules/padding-lines-in-component-definition.js b/tests/lib/rules/padding-lines-in-component-definition.js new file mode 100644 index 000000000..702897431 --- /dev/null +++ b/tests/lib/rules/padding-lines-in-component-definition.js @@ -0,0 +1,1291 @@ +/** + * @author ItMaga + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/padding-lines-in-component-definition') +const { + getTypeScriptFixtureTestOptions +} = require('../../test-utils/typescript') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('padding-lines-in-component-definition', rule, { + valid: [ + { + filename: 'Never.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'Setup.vue', + code: ` + import { ref, defineComponent } from 'vue'; + + `, + options: ['never'] + }, + { + filename: 'BetweenOptionsNever.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'never', + withinOption: 'never' + } + ] + }, + { + filename: 'GroupSingleLineProperties.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'always', + withinOption: 'never', + groupSingleLineProperties: true + } + ] + }, + { + filename: 'WithinOption.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + props: { + betweenItems: 'never', + withinEach: 'always' + }, + data: { + betweenItems: 'always', + withinEach: 'never' + } + } + } + ] + }, + { + filename: 'CustomOptions.vue', + code: ` + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + props: { + betweenItems: 'always', + withinEach: 'never' + }, + customOption: { + betweenItems: 'always' + } + } + } + ] + }, + { + filename: 'NewVue.js', + code: ` + new Vue({ + name: 'NewVue', + inheritAttrs: false, + props: { + foo: { + type: String, + required: true, + }, + bar: { + type: String, + required: true, + } + }, + customOption: { + getString() { return '1' }, + getNumber: () => 1, + }, + }) + `, + options: ['never'] + }, + { + filename: 'Mixin.js', + code: ` + Vue.mixin({ + name: 'Mixin', + inheritAttrs: false, + props: { + foo: { + type: String, + required: true, + }, + bar: { + type: String, + required: true, + } + }, + customOption: { + getString() { return '1' }, + getNumber: () => 1, + }, + }) + `, + options: [ + { + betweenOptions: 'never', + withinOption: 'ignore', + groupSingleLineProperties: true + } + ] + }, + { + filename: 'DefineProps.vue', + code: ` + import { defineProps } from 'vue' + + `, + options: ['never'] + }, + { + filename: 'DefineEmits.vue', + code: ` + import { defineEmits } from 'vue' + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + emits: { + betweenItems: 'always' + } + }, + groupSingleLineProperties: false + } + ] + }, + { + filename: 'Comment.vue', + code: ` + + `, + options: ['always'] + }, + { + filename: 'Spread.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'SpreadWithComment.vue', + code: ` + + `, + options: ['always'] + }, + { + filename: 'MyComment.vue', + code: ` + + `, + options: [{ betweenOptions: 'always', groupSingleLineProperties: false }] + }, + { + code: ` + `, + ...getTypeScriptFixtureTestOptions() + } + ], + invalid: [ + { + filename: 'Always.vue', + code: ` + + `, + output: ` + + `, + options: ['always'], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 5 + }, + { + message: 'Expected blank line before this definition.', + line: 10 + }, + { + message: 'Expected blank line before this definition.', + line: 15 + } + ] + }, + { + filename: 'Setup.vue', + code: ` + import { ref, defineComponent } from 'vue'; + + `, + output: ` + import { ref, defineComponent } from 'vue'; + + `, + options: ['never'], + errors: [ + { + message: 'Unexpected blank line before this definition.', + line: 7 + } + ] + }, + { + filename: 'BetweenOptionsAlways.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + output: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'always', + withinOption: 'never' + } + ], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 6 + } + ] + }, + { + filename: 'GroupSingleLineProperties.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + output: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'always', + withinOption: 'never', + groupSingleLineProperties: true + } + ], + errors: [ + { + message: 'Unexpected blank line between single line properties.', + line: 7 + } + ] + }, + { + filename: 'WithinOption.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + output: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + props: { + betweenItems: 'always', + withinEach: 'always' + }, + data: { + betweenItems: 'always', + withinEach: 'never' + } + } + } + ], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 10 + }, + { + message: 'Expected blank line before this definition.', + line: 12 + }, + { + message: 'Expected blank line before this definition.', + line: 14 + } + ] + }, + { + filename: 'CustomOptions.vue', + code: ` + + `, + output: ` + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + props: { + betweenItems: 'always', + withinEach: 'never' + }, + customOption: { + betweenItems: 'never' + } + } + } + ], + errors: [ + { + message: 'Unexpected blank line before this definition.', + line: 23 + } + ] + }, + { + filename: 'NewVue.js', + code: ` + new Vue({ + name: 'NewVue', + + inheritAttrs: false, + + props: { + foo: { + type: String, + required: true, + }, + bar: { + type: String, + required: true, + } + }, + customOption: { + getString() { return '1' }, + getNumber: () => 1, + }, + }) + `, + output: ` + new Vue({ + name: 'NewVue', + inheritAttrs: false, + + props: { + foo: { + type: String, + required: true, + }, + bar: { + type: String, + required: true, + } + }, + + customOption: { + getString() { return '1' }, + getNumber: () => 1, + }, + }) + `, + options: [ + { + betweenOptions: 'always', + withinOption: 'ignore', + groupSingleLineProperties: true + } + ], + errors: [ + { + message: 'Unexpected blank line between single line properties.', + line: 5 + }, + { + message: 'Expected blank line before this definition.', + line: 17 + } + ] + }, + { + filename: 'Mixin.js', + code: ` + Vue.mixin({ + name: 'NewVue', + inheritAttrs: false, + props: { + foo: { + type: String, + required: true, + }, + bar: { + type: String, + required: true, + } + }, + customOption: { + getString() { return '1' }, + getNumber: () => 1, + }, + }) + `, + output: ` + Vue.mixin({ + name: 'NewVue', + + inheritAttrs: false, + + props: { + foo: { + type: String, + required: true, + }, + bar: { + type: String, + required: true, + } + }, + + customOption: { + getString() { return '1' }, + getNumber: () => 1, + }, + }) + `, + options: [ + { + betweenOptions: 'always', + withinOption: 'ignore', + groupSingleLineProperties: false + } + ], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 4 + }, + { + message: 'Expected blank line before this definition.', + line: 5 + }, + { + message: 'Expected blank line before this definition.', + line: 15 + } + ] + }, + { + filename: 'DefineProps.vue', + code: ` + import { defineProps } from 'vue' + + `, + output: ` + import { defineProps } from 'vue' + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + props: { + betweenItems: 'always', + withinEach: 'never' + } + } + } + ], + errors: [ + { + message: 'Unexpected blank line before this definition.', + line: 8 + }, + { + message: 'Expected blank line before this definition.', + line: 10 + } + ] + }, + { + filename: 'DefineEmits.vue', + code: ` + import { defineEmits } from 'vue' + + `, + output: ` + import { defineEmits } from 'vue' + + `, + options: ['never'], + errors: [ + { + message: 'Unexpected blank line before this definition.', + line: 8 + } + ] + }, + { + filename: 'WithinOption.vue', + code: ` + import { defineComponent } from 'vue'; + + `, + output: ` + import { defineComponent } from 'vue'; + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + props: { + betweenItems: 'always', + withinEach: 'always' + } + }, + groupSingleLineProperties: true + } + ], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 5 + }, + { + message: 'Expected blank line before this definition.', + line: 9 + } + ] + }, + { + filename: 'Comment.vue', + code: ` + + `, + output: ` + + `, + options: ['always'], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 5 + }, + { + message: 'Expected blank line before this definition.', + line: 9 + }, + { + message: 'Expected blank line before this definition.', + line: 13 + }, + { + message: 'Unexpected blank line between single line properties.', + line: 21 + } + ] + }, + { + filename: 'Spread.vue', + code: ` + + `, + output: ` + + `, + options: ['always'], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 6 + }, + { + message: 'Expected blank line before this definition.', + line: 10 + }, + { + message: 'Expected blank line before this definition.', + line: 11 + } + ] + }, + { + filename: 'DefineWithSpreadAndComment.vue', + code: ` + import { defineEmits, defineProps } from 'vue' + + `, + output: ` + import { defineEmits, defineProps } from 'vue' + + `, + options: [ + { + betweenOptions: 'always', + withinOption: { + emits: 'always', + props: 'never' + }, + groupSingleLineProperties: false + } + ], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 7 + }, + { + message: 'Expected blank line before this definition.', + line: 8 + }, + { + message: 'Unexpected blank line before this definition.', + line: 16 + } + ] + }, + { + filename: 'MyComment.vue', + code: ` + + `, + output: ` + + `, + options: [{ betweenOptions: 'always', groupSingleLineProperties: false }], + errors: [ + { + message: 'Expected blank line before this definition.', + line: 5 + } + ] + } + ] +}) diff --git a/tests/lib/rules/prefer-define-options.js b/tests/lib/rules/prefer-define-options.js new file mode 100644 index 000000000..5b7f20e0d --- /dev/null +++ b/tests/lib/rules/prefer-define-options.js @@ -0,0 +1,135 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/prefer-define-options') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('prefer-define-options', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + `, + errors: [ + { + message: 'Use `defineOptions` instead of default export.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: null, + errors: [ + { + message: 'Use `defineOptions` instead of default export.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + message: 'Use `defineOptions` instead of default export.', + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + `, + errors: [ + { + message: 'Use `defineOptions` instead of default export.', + line: 7 + } + ] + } + ] +}) diff --git a/tests/lib/rules/prefer-import-from-vue.js b/tests/lib/rules/prefer-import-from-vue.js new file mode 100644 index 000000000..519d0a877 --- /dev/null +++ b/tests/lib/rules/prefer-import-from-vue.js @@ -0,0 +1,137 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/prefer-import-from-vue') + +const tester = new RuleTester({ + languageOptions: { + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('prefer-import-from-vue', rule, { + valid: [ + `import { createApp } from 'vue'`, + `import { ref, reactive } from '@vue/composition-api'`, + `export { createApp } from 'vue'`, + `export * from 'vue'`, + `import Foo from 'foo'`, + `import { createApp } from 'vue' + export { createApp }`, + { + filename: 'test.d.ts', + code: `import '@vue/runtime-dom'` + } + ], + invalid: [ + { + code: `import { createApp } from '@vue/runtime-dom'`, + output: `import { createApp } from 'vue'`, + errors: [ + { + message: "Import from 'vue' instead of '@vue/runtime-dom'.", + line: 1, + column: 27 + } + ] + }, + { + code: `import { computed } from '@vue/runtime-core'`, + output: `import { computed } from 'vue'`, + errors: [ + { + message: "Import from 'vue' instead of '@vue/runtime-core'.", + line: 1, + column: 26 + } + ] + }, + { + code: `import { computed } from '@vue/reactivity'`, + output: `import { computed } from 'vue'`, + errors: [ + { + message: "Import from 'vue' instead of '@vue/reactivity'.", + line: 1, + column: 26 + } + ] + }, + { + code: `import { normalizeClass } from '@vue/shared'`, + output: `import { normalizeClass } from 'vue'`, + errors: [ + { + message: "Import from 'vue' instead of '@vue/shared'.", + line: 1, + column: 32 + } + ] + }, + { + code: `import { unknown } from '@vue/reactivity'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/reactivity'."] + }, + { + code: `import { unknown } from '@vue/runtime-dom'`, + output: `import { unknown } from 'vue'`, + errors: ["Import from 'vue' instead of '@vue/runtime-dom'."] + }, + { + code: `import * as Foo from '@vue/reactivity'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/reactivity'."] + }, + { + code: `import * as Foo from '@vue/runtime-dom'`, + output: `import * as Foo from 'vue'`, + errors: ["Import from 'vue' instead of '@vue/runtime-dom'."] + }, + { + code: `export * from '@vue/reactivity'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/reactivity'."] + }, + { + code: `export * from '@vue/runtime-dom'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/runtime-dom'."] + }, + { + code: `export { computed } from '@vue/reactivity'`, + output: `export { computed } from 'vue'`, + errors: ["Import from 'vue' instead of '@vue/reactivity'."] + }, + { + code: `export { computed } from '@vue/runtime-dom'`, + output: `export { computed } from 'vue'`, + errors: ["Import from 'vue' instead of '@vue/runtime-dom'."] + }, + { + code: `export { unknown } from '@vue/reactivity'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/reactivity'."] + }, + { + code: `export { unknown } from '@vue/runtime-dom'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/runtime-dom'."] + }, + { + code: `import unknown from '@vue/reactivity'`, + output: null, + errors: ["Import from 'vue' instead of '@vue/reactivity'."] + }, + { + code: `import unknown from '@vue/runtime-dom'`, + output: `import unknown from 'vue'`, + errors: ["Import from 'vue' instead of '@vue/runtime-dom'."] + } + ] +}) diff --git a/tests/lib/rules/prefer-prop-type-boolean-first.js b/tests/lib/rules/prefer-prop-type-boolean-first.js new file mode 100644 index 000000000..e01759450 --- /dev/null +++ b/tests/lib/rules/prefer-prop-type-boolean-first.js @@ -0,0 +1,328 @@ +/** + * @author Pig Fang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/prefer-prop-type-boolean-first') +const { + getTypeScriptFixtureTestOptions +} = require('../../test-utils/typescript') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('prefer-prop-type-boolean-first', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + code: ` + `, + ...getTypeScriptFixtureTestOptions() + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 5, + column: 29, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 5, + column: 37, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 5, + column: 29, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 5, + column: 37, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 4, + column: 27, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 4, + column: 35, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 4, + column: 35, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'shouldBeFirst', + line: 4, + column: 43, + suggestions: [ + { + messageId: 'moveToFirst', + output: ` + + ` + } + ] + } + ] + } + ] +}) 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..5b5a508ac --- /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-compat').RuleTester +const rule = require('../../../lib/rules/prefer-separate-static-class') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + 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 + } + ] + } + ] +}) diff --git a/tests/lib/rules/prefer-template.js b/tests/lib/rules/prefer-template.js index e35fa6d1f..0c4b3366b 100644 --- a/tests/lib/rules/prefer-template.js +++ b/tests/lib/rules/prefer-template.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/prefer-template') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2020 } }) tester.run('prefer-template', rule, { diff --git a/tests/lib/rules/prefer-true-attribute-shorthand.js b/tests/lib/rules/prefer-true-attribute-shorthand.js new file mode 100644 index 000000000..80965bac6 --- /dev/null +++ b/tests/lib/rules/prefer-true-attribute-shorthand.js @@ -0,0 +1,395 @@ +/** + * @author Pig Fang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/prefer-true-attribute-shorthand') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('prefer-true-attribute-shorthand', 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: ` + + `, + options: ['always'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['always', { except: ['value', '/^foo-/'] }] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['never', { except: ['value', '/^foo-/'] }] + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + `, + output: null, + errors: [ + { + messageId: 'expectShort', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoShort', + output: ` + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: null, + errors: [ + { + messageId: 'expectShort', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoShort', + output: ` + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: null, + options: ['always'], + errors: [ + { + messageId: 'expectShort', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoShort', + output: ` + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: null, + options: ['always'], + errors: [ + { + messageId: 'expectShort', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoShort', + output: ` + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: null, + options: ['never'], + errors: [ + { + messageId: 'expectLong', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoLongVueProp', + output: ` + ` + }, + { + messageId: 'rewriteIntoLongHtmlAttr', + output: ` + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: null, + options: ['always', { except: ['value', '/^foo-/'] }], + errors: [ + { + messageId: 'expectLong', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoLongVueProp', + output: ` + ` + }, + { + messageId: 'rewriteIntoLongHtmlAttr', + output: ` + ` + } + ] + }, + { + messageId: 'expectLong', + line: 3, + column: 23, + suggestions: [ + { + messageId: 'rewriteIntoLongVueProp', + output: ` + ` + }, + { + messageId: 'rewriteIntoLongHtmlAttr', + output: ` + ` + } + ] + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: null, + options: ['never', { except: ['value', '/^foo-/'] }], + errors: [ + { + messageId: 'expectShort', + line: 3, + column: 17, + suggestions: [ + { + messageId: 'rewriteIntoShort', + output: ` + ` + } + ] + }, + { + messageId: 'expectShort', + line: 3, + column: 31, + suggestions: [ + { + messageId: 'rewriteIntoShort', + output: ` + ` + } + ] + } + ] + } + ] +}) diff --git a/tests/lib/rules/prefer-use-template-ref.js b/tests/lib/rules/prefer-use-template-ref.js new file mode 100644 index 000000000..afc6d6a11 --- /dev/null +++ b/tests/lib/rules/prefer-use-template-ref.js @@ -0,0 +1,513 @@ +/** + * @author Thomasan1999 + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/prefer-use-template-ref') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('prefer-use-template-ref', rule, { + valid: [ + { + filename: 'single-use-template-ref.vue', + code: ` + + + ` + }, + { + filename: 'multiple-use-template-refs.vue', + code: ` + + + ` + }, + { + filename: 'use-template-ref-in-block.vue', + code: ` + + + ` + }, + { + filename: 'non-template-ref.vue', + code: ` + + + ` + }, + { + filename: 'counter.js', + code: ` + import { ref } from 'vue'; + const counter = ref(0); + const names = ref(new Set()); + function incrementCounter() { + counter.value++; + return counter.value; + } + function storeName(name) { + names.value.add(name) + } + ` + }, + { + filename: 'setup-function.vue', + code: ` + + + ` + }, + { + filename: 'options-api-no-refs.vue', + code: ` + `, + options: [ + { + externalIgnores: ['IgnoreTag'] + } + ] + }, + { + code: ` + `, + options: [ + { + externalIgnores: ['IgnoreTag'] + } + ] + } ], invalid: [ { @@ -380,7 +397,6 @@ content
singleline content
`, - options: [{ ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.', 'Expected 1 line break before closing tag (`
`), but no line breaks found.' @@ -399,7 +416,6 @@ singleline content singlelinechildren `, - options: [{ ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (``), but no line breaks found.', 'Expected 1 line break after opening tag (``), but no line breaks found.', @@ -426,7 +443,6 @@ children
`, - options: [{ ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.', 'Expected 1 line break before closing tag (`
`), but no line breaks found.' @@ -445,7 +462,6 @@ children
singleline element
`, - options: [{ ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.', 'Expected 1 line break before closing tag (`
`), but no line breaks found.' @@ -464,13 +481,13 @@ singleline element
`, - options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.' ] @@ -481,13 +498,13 @@ singleline element
`, - options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], output: ` `, + options: [{ ignoreWhenEmpty: false, ignoreWhenNoAttributes: false }], errors: [ 'Expected 1 line break after opening tag (`
`), but no line breaks found.' ] diff --git a/tests/lib/rules/slot-name-casing.js b/tests/lib/rules/slot-name-casing.js new file mode 100644 index 000000000..ea8b72aab --- /dev/null +++ b/tests/lib/rules/slot-name-casing.js @@ -0,0 +1,148 @@ +/** + * @author WayneZhang + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/slot-name-casing') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('slot-name-casing', rule, { + valid: [ + ``, + ``, + ``, + ``, + ``, + { + filename: 'test.vue', + code: ` + + `, + options: ['kebab-case'] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['singleword'] + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + messageId: 'invalidCase', + data: { + name: 'foo-bar', + caseType: 'camelCase' + }, + line: 3, + column: 17 + }, + { + messageId: 'invalidCase', + data: { + name: 'foo-Bar_baz', + caseType: 'camelCase' + }, + line: 4, + column: 17 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['kebab-case'], + errors: [ + { + messageId: 'invalidCase', + data: { + name: 'fooBar', + caseType: 'kebab-case' + }, + line: 3, + column: 17 + }, + { + messageId: 'invalidCase', + data: { + name: 'foo-Bar_baz', + caseType: 'kebab-case' + }, + line: 4, + column: 17 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + options: ['singleword'], + errors: [ + { + messageId: 'invalidCase', + data: { + name: 'foo-bar', + caseType: 'singleword' + }, + line: 3, + column: 17 + }, + { + messageId: 'invalidCase', + data: { + name: 'fooBar', + caseType: 'singleword' + }, + line: 4, + column: 17 + }, + { + messageId: 'invalidCase', + data: { + name: 'foo-Bar_baz', + caseType: 'singleword' + }, + line: 5, + column: 17 + } + ] + } + ] +}) diff --git a/tests/lib/rules/sort-keys.js b/tests/lib/rules/sort-keys.js index 83e59cc41..030c5ed3f 100644 --- a/tests/lib/rules/sort-keys.js +++ b/tests/lib/rules/sort-keys.js @@ -5,11 +5,11 @@ 'use strict' const rule = require('../../../lib/rules/sort-keys') -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const ruleTester = new RuleTester() -const parserOptions = { +const languageOptions = { ecmaVersion: 2018, sourceType: 'module' } @@ -28,7 +28,7 @@ ruleTester.run('sort-keys', rule, { } } `, - parserOptions + languageOptions }, { filename: 'test.vue', @@ -46,7 +46,7 @@ ruleTester.run('sort-keys', rule, { }, } `, - parserOptions + languageOptions }, { filename: 'propsOrder.vue', @@ -69,21 +69,21 @@ ruleTester.run('sort-keys', rule, { }, } `, - parserOptions + languageOptions }, { filename: 'test.vue', code: ` export default {} `, - parserOptions + languageOptions }, { filename: 'test.vue', code: ` export default 'example-text' `, - parserOptions + languageOptions }, { filename: 'test.jsx', @@ -97,14 +97,14 @@ ruleTester.run('sort-keys', rule, { }, } `, - parserOptions + languageOptions }, { filename: 'test.js', code: ` Vue.component('example') `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', @@ -120,7 +120,7 @@ ruleTester.run('sort-keys', rule, { } }) `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', @@ -136,31 +136,31 @@ ruleTester.run('sort-keys', rule, { } }) `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { filename: 'test.js', code: ` new Vue() `, - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, // default (asc) { code: "var obj = {'':1, [``]:2}", options: [], - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { code: "var obj = {[``]:1, '':2}", options: [], - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { code: "var obj = {'':1, a:2}", options: [] }, { code: 'var obj = {[``]:1, a:2}', options: [], - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { code: 'var obj = {_:2, a:1, b:3} // default', options: [] }, { code: 'var obj = {a:1, b:3, c:2}', options: [] }, @@ -174,92 +174,96 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {a:1, b:3, [a + b]: -1, c:2}', options: [], - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { code: "var obj = {'':1, [f()]:2, a:3}", options: [], - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, { code: "var obj = {a:1, [b++]:2, '':3}", options: ['desc'], - parserOptions: { ecmaVersion: 6 } + languageOptions: { ecmaVersion: 6 } }, // ignore properties separated by spread properties { code: 'var obj = {a:1, ...z, b:1}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {b:1, ...z, a:1}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {...a, b:1, ...c, d:1}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {...a, b:1, ...d, ...c, e:2, z:5}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {b:1, ...c, ...d, e:2}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: "var obj = {a:1, ...z, '':2}", options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: "var obj = {'':1, ...z, 'a':2}", options: ['desc'], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, // not ignore properties not separated by spread properties { code: 'var obj = {...z, a:1, b:1}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {...z, ...c, a:1, b:1}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {a:1, b:1, ...z}', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'var obj = {...z, ...x, a:1, ...c, ...d, f:5, e:4}', options: ['desc'], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, // works when spread occurs somewhere other than an object literal { code: 'function fn(...args) { return [...args].length; }', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, { code: 'function g() {}; function f(...args) { return g(...args); }', options: [], - parserOptions: { ecmaVersion: 2018 } + languageOptions: { ecmaVersion: 2018 } }, // ignore destructuring patterns. - { code: 'let {a, b} = {}', options: [], parserOptions: { ecmaVersion: 6 } }, + { + code: 'let {a, b} = {}', + options: [], + languageOptions: { ecmaVersion: 6 } + }, // nested { code: 'var obj = {a:1, b:{x:1, y:1}, c:1}', options: [] }, @@ -519,7 +523,7 @@ ruleTester.run('sort-keys', rule, { }, { code: 'var obj = {a:1, [``]:2} // default', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ "Expected object keys to be in ascending order. '' should be before 'a'." ] @@ -571,7 +575,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {...z, c:1, b:1}', options: [], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in ascending order. 'b' should be before 'c'." ] @@ -579,7 +583,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {...z, ...c, d:4, b:1, ...y, ...f, e:2, a:1}', options: [], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in ascending order. 'b' should be before 'd'.", "Expected object keys to be in ascending order. 'a' should be before 'e'." @@ -588,7 +592,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {c:1, b:1, ...a}', options: [], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in ascending order. 'b' should be before 'c'." ] @@ -596,7 +600,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {...z, ...a, c:1, b:1}', options: [], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in ascending order. 'b' should be before 'c'." ] @@ -604,7 +608,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {...z, b:1, a:1, ...d, ...c}', options: [], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in ascending order. 'a' should be before 'b'." ] @@ -612,7 +616,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {...z, a:2, b:0, ...x, ...c}', options: ['desc'], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in descending order. 'b' should be before 'a'." ] @@ -620,7 +624,7 @@ ruleTester.run('sort-keys', rule, { { code: 'var obj = {...z, a:2, b:0, ...x}', options: ['desc'], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in descending order. 'b' should be before 'a'." ] @@ -628,7 +632,7 @@ ruleTester.run('sort-keys', rule, { { code: "var obj = {...z, '':1, a:2}", options: ['desc'], - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { ecmaVersion: 2018 }, errors: [ "Expected object keys to be in descending order. 'a' should be before ''." ] @@ -637,7 +641,7 @@ ruleTester.run('sort-keys', rule, { // ignore non-simple computed properties, but their position shouldn't affect other comparisons. { code: "var obj = {a:1, [b+c]:2, '':3}", - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ "Expected object keys to be in ascending order. '' should be before 'a'." ] @@ -645,7 +649,7 @@ ruleTester.run('sort-keys', rule, { { code: "var obj = {'':1, [b+c]:2, a:3}", options: ['desc'], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ "Expected object keys to be in descending order. 'a' should be before ''." ] @@ -653,7 +657,7 @@ ruleTester.run('sort-keys', rule, { { code: "var obj = {b:1, [f()]:2, '':3, a:4}", options: ['desc'], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ "Expected object keys to be in descending order. 'a' should be before ''." ] @@ -662,7 +666,7 @@ ruleTester.run('sort-keys', rule, { // not ignore simple computed properties. { code: 'var obj = {a:1, b:3, [a]: -1, c:2}', - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ "Expected object keys to be in ascending order. 'a' should be before 'b'." ] @@ -914,7 +918,7 @@ ruleTester.run('sort-keys', rule, { { code: "var obj = {[``]:1, a:'2'} // desc", options: ['desc'], - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ "Expected object keys to be in descending order. 'a' should be before ''." ] @@ -1189,7 +1193,7 @@ ruleTester.run('sort-keys', rule, { }, } `, - parserOptions, + languageOptions, errors: [ { @@ -1224,7 +1228,7 @@ ruleTester.run('sort-keys', rule, { name: 'burger', }; `, - parserOptions, + languageOptions, errors: [ { message: @@ -1257,7 +1261,7 @@ ruleTester.run('sort-keys', rule, { name: 'burger', }; `, - parserOptions, + languageOptions, errors: [ { message: @@ -1286,7 +1290,7 @@ ruleTester.run('sort-keys', rule, { name: 'burger', }; `, - parserOptions, + languageOptions, errors: [ { message: @@ -1315,7 +1319,7 @@ ruleTester.run('sort-keys', rule, { const dict = { zd: 1, a: 2 }; `, - parserOptions, + languageOptions, errors: [ { message: @@ -1346,7 +1350,7 @@ ruleTester.run('sort-keys', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -1377,7 +1381,7 @@ ruleTester.run('sort-keys', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -1408,7 +1412,7 @@ ruleTester.run('sort-keys', rule, { template: '
' }) `, - parserOptions: { ecmaVersion: 6 }, + languageOptions: { ecmaVersion: 6 }, errors: [ { message: @@ -1436,7 +1440,7 @@ ruleTester.run('sort-keys', rule, { } `, options: ['asc', { ignoreGrandchildrenOf: [] }], - parserOptions, + languageOptions, errors: [ { message: @@ -1459,7 +1463,7 @@ ruleTester.run('sort-keys', rule, { a: 2 } `, - parserOptions, + languageOptions, errors: [ { message: @@ -1487,7 +1491,7 @@ ruleTester.run('sort-keys', rule, { } } `, - parserOptions, + languageOptions, errors: [ { message: diff --git a/tests/lib/rules/space-in-parens.js b/tests/lib/rules/space-in-parens.js index 8791652ed..d6218216d 100644 --- a/tests/lib/rules/space-in-parens.js +++ b/tests/lib/rules/space-in-parens.js @@ -19,8 +19,7 @@ const errorMessage = semver.lt(ESLint.version, '6.4.0') : (obj) => obj const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('space-in-parens', rule, { @@ -94,13 +93,13 @@ tester.run('space-in-parens', rule, { @click="foo(arg)" /> `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', @@ -143,13 +142,13 @@ tester.run('space-in-parens', rule, { :value="(1 + 2) + 3" > `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', @@ -192,13 +191,13 @@ tester.run('space-in-parens', rule, { :[(1+2)]="(1 + 2) + 3" > `, - options: ['always'], output: ` `, + options: ['always'], errors: [ errorMessage({ messageId: 'missingOpeningSpace', diff --git a/tests/lib/rules/space-infix-ops.js b/tests/lib/rules/space-infix-ops.js index b8a679787..0325884a7 100644 --- a/tests/lib/rules/space-infix-ops.js +++ b/tests/lib/rules/space-infix-ops.js @@ -8,8 +8,7 @@ const semver = require('semver') const rule = require('../../../lib/rules/space-infix-ops') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) const message = semver.lt(ESLint.version, '5.10.0') diff --git a/tests/lib/rules/space-unary-ops.js b/tests/lib/rules/space-unary-ops.js index 6b3e7c25f..8229e78cd 100644 --- a/tests/lib/rules/space-unary-ops.js +++ b/tests/lib/rules/space-unary-ops.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/space-unary-ops') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('space-unary-ops', rule, { @@ -50,8 +49,8 @@ tester.run('space-unary-ops', rule, { }, { code: '', - options: [{ nonwords: true }], output: '', + options: [{ nonwords: true }], errors: ["Unary operator '!' must be followed by whitespace."] }, diff --git a/tests/lib/rules/static-class-names-order.js b/tests/lib/rules/static-class-names-order.js index 6028561fd..0f293f5e9 100644 --- a/tests/lib/rules/static-class-names-order.js +++ b/tests/lib/rules/static-class-names-order.js @@ -4,20 +4,11 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - const rule = require('../../../lib/rules/static-class-names-order') -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ +const RuleTester = require('../../eslint-compat').RuleTester const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('static-class-names-order', rule, { valid: [ diff --git a/tests/lib/rules/template-curly-spacing.js b/tests/lib/rules/template-curly-spacing.js index 6c4376969..c806bfafc 100644 --- a/tests/lib/rules/template-curly-spacing.js +++ b/tests/lib/rules/template-curly-spacing.js @@ -3,12 +3,11 @@ */ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/template-curly-spacing') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2020 } }) tester.run('template-curly-spacing', rule, { @@ -41,14 +40,13 @@ tester.run('template-curly-spacing', rule, { }, // CSS vars injection - { - code: ` + ` ` - } + + ` ], invalid: [ { @@ -79,12 +77,12 @@ tester.run('template-curly-spacing', rule, {
`, - options: ['always'], output: ` `, + options: ['always'], errors: [ { message: "Expected space(s) after '${'.", diff --git a/tests/lib/rules/this-in-template.js b/tests/lib/rules/this-in-template.js index 5d081f468..53896794e 100644 --- a/tests/lib/rules/this-in-template.js +++ b/tests/lib/rules/this-in-template.js @@ -4,21 +4,12 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - const rule = require('../../../lib/rules/this-in-template') -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ +const RuleTester = require('../../eslint-compat').RuleTester const ruleTester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2020 } }) function createValidTests(prefix, options) { @@ -52,10 +43,6 @@ function createValidTests(prefix, options) { code: ``, options }, - { - code: ``, - options - }, { code: ``, options @@ -76,26 +63,6 @@ function createValidTests(prefix, options) { code: ``, options }, - { - code: ``, - options - }, - { - code: ``, - options - }, - { - code: ``, - options - }, - { - code: ``, - options - }, - { - code: ``, - options - }, { code: ``, options - }, - - // We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier. - { - code: ``, - options } ] } @@ -198,84 +159,115 @@ function createInvalidTests(prefix, options, message, type) { // errors: [{ message, type }], // options // } - ].concat( - options[0] === 'always' - ? [] - : [ - { - code: ``, - output: ``, - errors: [{ message, type }], - options - }, - { - code: ``, - output: ``, - 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']), + ...[[], ['never'], ['always']].flatMap((options) => { + const comment = options.join('') + return [ + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + { + code: ``, + options + }, + // We cannot use `.` in dynamic arguments because the right of the `.` becomes a modifier. + { + code: ``, + options + } + ] + }) + ], + 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'), + ...[[], ['never']].flatMap((options) => { + const comment = options.join('') + const message = "Unexpected usage of 'this'." + const type = 'ThisExpression' + return [ + { + code: ``, + output: ``, + errors: [{ message, type }], + options + }, + { + code: ``, + output: ``, + errors: [{ message, type }], + options + } + ] + }), + { + code: ``, + output: ``, + options: ['never'], + errors: ["Unexpected usage of 'this'."] + }, + { + code: ``, + output: ``, + options: ['never'], + errors: ["Unexpected usage of 'this'."] + } + ] }) 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/use-v-on-exact.js b/tests/lib/rules/use-v-on-exact.js index b080faa87..757a1a5c9 100644 --- a/tests/lib/rules/use-v-on-exact.js +++ b/tests/lib/rules/use-v-on-exact.js @@ -4,111 +4,58 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - const rule = require('../../../lib/rules/use-v-on-exact') -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ +const RuleTester = require('../../eslint-compat').RuleTester const ruleTester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) ruleTester.run('use-v-on-exact', rule, { valid: [ - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: `` - }, - { - code: ` + ` ], invalid: [ diff --git a/tests/lib/rules/v-bind-style.js b/tests/lib/rules/v-bind-style.js index f7c67d6f9..f65099530 100644 --- a/tests/lib/rules/v-bind-style.js +++ b/tests/lib/rules/v-bind-style.js @@ -5,22 +5,16 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/v-bind-style') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) +const expectedShorthand = 'Expected same-name shorthand.' +const unexpectedShorthand = 'Unexpected same-name shorthand.' + tester.run('v-bind-style', rule, { valid: [ { @@ -43,7 +37,7 @@ tester.run('v-bind-style', rule, { { filename: 'test.vue', code: '', - options: ['longform'] + options: ['longform', { sameNameShorthand: 'ignore' }] }, // Don't enforce `.prop` shorthand because of experimental. @@ -64,6 +58,78 @@ tester.run('v-bind-style', rule, { filename: 'test.vue', code: '', options: ['shorthand'] + }, + // same-name shorthand: never + { + filename: 'test.vue', + code: '', + options: ['shorthand', { sameNameShorthand: 'never' }] + }, + { + filename: 'test.vue', + code: '', + options: ['longform', { sameNameShorthand: 'never' }] + }, + { + // modifier + filename: 'test.vue', + code: ` + + `, + options: ['shorthand', { sameNameShorthand: 'never' }] + }, + { + filename: 'test.vue', + code: '', + options: ['longform', { sameNameShorthand: 'never' }] + }, + { + // camel case + filename: 'test.vue', + code: '', + options: ['shorthand', { sameNameShorthand: 'never' }] + }, + // same-name shorthand: always + { + filename: 'test.vue', + code: '', + options: ['shorthand', { sameNameShorthand: 'always' }] + }, + { + filename: 'test.vue', + code: '', + options: ['longform', { sameNameShorthand: 'always' }] + }, + { + // modifier + filename: 'test.vue', + code: ` + + `, + options: ['shorthand', { sameNameShorthand: 'always' }] + }, + { + filename: 'test.vue', + code: '', + options: ['longform', { sameNameShorthand: 'always' }] + }, + { + // camel case + filename: 'test.vue', + code: '', + options: ['shorthand', { sameNameShorthand: 'always' }] + }, + { + // https://github.com/vuejs/eslint-plugin-vue/issues/2409 + filename: 'test.vue', + code: '', + options: ['shorthand', { sameNameShorthand: 'always' }] } ], invalid: [ @@ -75,45 +141,158 @@ tester.run('v-bind-style', rule, { }, { filename: 'test.vue', - options: ['shorthand'], code: '', output: '', + options: ['shorthand'], errors: ["Unexpected 'v-bind' before ':'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind' before ':'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-bind:' instead of '.'."] + }, + // v-bind same-name shorthand (Vue 3.4+) + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand'], + errors: ["Unexpected 'v-bind' before ':'."] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['longform'], + errors: ["Expected 'v-bind' before ':'."] + }, + // same-name shorthand: never + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['longform', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand] + }, + { + // modifier + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['longform', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand, "Expected 'v-bind:' instead of '.'."] + }, + { + // camel case + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand] + }, + { + // https://github.com/vuejs/eslint-plugin-vue/issues/2409 + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'never' }], + errors: [unexpectedShorthand] + }, + // same-name shorthand: always + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'always' }], + errors: [expectedShorthand] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'always' }], + errors: [expectedShorthand] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['longform', { sameNameShorthand: 'always' }], + errors: [expectedShorthand] + }, + { + // modifier + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'always' }], + errors: [expectedShorthand] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'always' }], + errors: [expectedShorthand] + }, + { + // camel case + filename: 'test.vue', + code: '', + output: '', + options: ['shorthand', { sameNameShorthand: 'always' }], + errors: [expectedShorthand] } ] }) diff --git a/tests/lib/rules/v-for-delimiter-style.js b/tests/lib/rules/v-for-delimiter-style.js index 4b232a290..ebec2df56 100644 --- a/tests/lib/rules/v-for-delimiter-style.js +++ b/tests/lib/rules/v-for-delimiter-style.js @@ -6,20 +6,11 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/v-for-delimiter-style') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('v-for-delimiter-style', rule, { @@ -44,6 +35,19 @@ tester.run('v-for-delimiter-style', rule, { filename: 'test.vue', code: '' }, + { + // https://github.com/vuejs/vue-eslint-parser/issues/226 + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + }, { filename: 'test.vue', code: '', @@ -67,6 +71,17 @@ tester.run('v-for-delimiter-style', rule, { } ] }, + { + filename: 'test.vue', + code: '', + output: '', + errors: [ + { + message: "Expected 'in' instead of 'of' in 'v-for'.", + column: 23 + } + ] + }, { filename: 'test.vue', code: '', @@ -102,9 +117,9 @@ tester.run('v-for-delimiter-style', rule, { }, { filename: 'test.vue', - options: ['in'], code: '', output: '', + options: ['in'], errors: [ { message: "Expected 'in' instead of 'of' in 'v-for'.", @@ -114,9 +129,9 @@ tester.run('v-for-delimiter-style', rule, { }, { filename: 'test.vue', - options: ['of'], code: '', output: '', + options: ['of'], errors: [ { message: "Expected 'of' instead of 'in' in 'v-for'.", diff --git a/tests/lib/rules/v-if-else-key.js b/tests/lib/rules/v-if-else-key.js new file mode 100644 index 000000000..46c42f52f --- /dev/null +++ b/tests/lib/rules/v-if-else-key.js @@ -0,0 +1,581 @@ +/** + * @author Felipe Melendez + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/v-if-else-key') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('v-if-else-key', 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: + "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.", + line: 4 + }, + { + message: + "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.", + line: 5 + }, + { + message: + "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.", + line: 6 + }, + { + message: + "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.", + line: 8 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + message: + "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.", + line: 4 + }, + { + message: + "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.", + line: 5 + }, + { + message: + "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.", + line: 8 + }, + { + message: + "Conditionally rendered repeated component 'OuterComponent' expected to have a 'key' attribute.", + line: 10 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + message: + "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.", + line: 5 + }, + { + message: + "Conditionally rendered repeated component 'InnerComponent' expected to have a 'key' attribute.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 4 + }, + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 5 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 4 + }, + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 6 + } + ] + }, + { + filename: 'test.vue', + code: ` + + + `, + output: ` + + + `, + errors: [ + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 4 + }, + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 5 + }, + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 7 + }, + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 8 + }, + { + message: + "Conditionally rendered repeated component 'ComponentA' expected to have a 'key' attribute.", + line: 9 + } + ] + } + ] +}) diff --git a/tests/lib/rules/v-on-event-hyphenation.js b/tests/lib/rules/v-on-event-hyphenation.js index 087e5d00a..3f58ce1f0 100644 --- a/tests/lib/rules/v-on-event-hyphenation.js +++ b/tests/lib/rules/v-on-event-hyphenation.js @@ -1,13 +1,10 @@ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/v-on-event-hyphenation.js') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { - ecmaVersion: 2019 - } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2019 } }) tester.run('v-on-event-hyphenation', rule, { @@ -47,6 +44,32 @@ tester.run('v-on-event-hyphenation', rule, { `, options: ['never', { ignore: ['custom'] }] + }, + { + code: ` + + `, + options: ['never', { ignore: ['custom-event'] }] + }, + { + code: ` + + `, + options: ['never', { ignoreTags: ['/^Vue/', 'custom-component'] }] + }, + { + code: ` + + `, + options: ['always', { ignoreTags: ['/^Vue/', 'custom-component'] }] } ], invalid: [ @@ -73,12 +96,12 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: [ { message: "v-on event '@customEvent' must be hyphenated.", @@ -95,12 +118,12 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: ["v-on event 'v-on:custom-event' can't be hyphenated."] }, { @@ -110,13 +133,13 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: ["v-on event '@update:modelValue' must be hyphenated."] }, { @@ -126,13 +149,13 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: ["v-on event '@update:model-value' can't be hyphenated."] }, { @@ -144,7 +167,6 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['always', { autofix: true }], output: ` `, + options: ['always', { autofix: true }], errors: [ "v-on event '@upDate:modelValue' must be hyphenated.", "v-on event '@up-date:modelValue' must be hyphenated.", @@ -168,7 +191,6 @@ tester.run('v-on-event-hyphenation', rule, { `, - options: ['never', { autofix: true }], output: ` `, + options: ['never', { autofix: true }], errors: [ "v-on event '@up-date:modelValue' can't be hyphenated.", "v-on event '@upDate:model-value' can't be hyphenated.", "v-on event '@up-date:model-value' can't be hyphenated." ] + }, + { + code: ` + + `, + output: ` + + `, + options: ['never', { autofix: true, ignoreTags: ['CustomComponent'] }], + errors: [ + { + message: "v-on event 'v-on:custom-event' can't be hyphenated.", + line: 3, + column: 23 + } + ] + }, + { + code: ` + + `, + output: ` + + `, + options: ['always', { autofix: true, ignoreTags: ['CustomComponent'] }], + errors: [ + { + message: "v-on event 'v-on:customEvent' must be hyphenated.", + line: 3, + column: 23 + } + ] } ] }) diff --git a/tests/lib/rules/v-on-function-call.js b/tests/lib/rules/v-on-function-call.js deleted file mode 100644 index cca7834da..000000000 --- a/tests/lib/rules/v-on-function-call.js +++ /dev/null @@ -1,361 +0,0 @@ -/** - * @author Niklas Higi - */ -'use strict' - -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester -const rule = require('../../../lib/rules/v-on-function-call') - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - -const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2020, sourceType: 'module' } -}) - -tester.run('v-on-function-call', rule, { - valid: [ - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '', - options: ['always'] - }, - { - filename: 'test.vue', - code: '', - options: ['never'] - }, - { - filename: 'test.vue', - code: '', - options: ['always'] - }, - { - filename: 'test.vue', - code: '', - options: ['never'] - }, - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '', - options: ['always'] - }, - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '', - options: ['always'] - }, - { - filename: 'test.vue', - code: '' - }, - { - filename: 'test.vue', - code: '', - options: ['always'] - }, - { - filename: 'test.vue', - code: ` - `, - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - options: ['never', { ignoreIncludesComment: true }] - }, - { - filename: 'test.vue', - code: '', - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - - `, - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - - `, - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - - `, - options: ['never'] - } - ], - invalid: [ - { - filename: 'test.vue', - code: '', - output: null, - errors: [ - "Method calls inside of 'v-on' directives must have parentheses." - ], - options: ['always'] - }, - { - filename: 'test.vue', - code: '', - output: ``, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: '', - output: ``, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: '', - output: null, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - output: null, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses.", - "Method calls without arguments inside of 'v-on' directives must not have parentheses.", - "Method calls without arguments inside of 'v-on' directives must not have parentheses.", - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - output: ` - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - output: ` - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - output: ` - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses.", - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - output: ` - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - `, - output: ` - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: '', - output: '', - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - - `, - output: ` - - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - }, - { - filename: 'test.vue', - code: ` - - `, - output: ` - - `, - errors: [ - "Method calls without arguments inside of 'v-on' directives must not have parentheses." - ], - options: ['never'] - } - ] -}) diff --git a/tests/lib/rules/v-on-handler-style.js b/tests/lib/rules/v-on-handler-style.js new file mode 100644 index 000000000..a169402f7 --- /dev/null +++ b/tests/lib/rules/v-on-handler-style.js @@ -0,0 +1,1141 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/v-on-handler-style') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2020, + sourceType: 'module' + } +}) + +tester.run('v-on-handler-style', rule, { + valid: [ + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: ``, + options: [['method', 'inline-function']] + }, + { + filename: 'test.vue', + code: `` + }, + { + filename: 'test.vue', + code: ``, + options: [['method', 'inline']] + }, + { + filename: 'test.vue', + code: ``, + options: ['inline'] + }, + { + filename: 'test.vue', + code: ``, + options: ['inline-function'] + }, + { + filename: 'test.vue', + code: '' + }, + { + filename: 'test.vue', + code: '' + } + ], + invalid: [ + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: ['inline'], + errors: [ + { + message: 'Prefer inline handler over method handler in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 4, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over method handler in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 3, + column: 25 + } + ] + }, + // ['method', 'inline-function'] + { + filename: 'test.vue', + code: '', + output: ``, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 1, + column: 24 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline-function'], { ignoreIncludesComment: true }], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 38 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 24 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 5, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 5, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 6, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 23 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 22 + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 26 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 33 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: ` + + `, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 33 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 25 + }, + { + message: + 'Prefer method handler over inline handler in v-on. Note that you may need to create a new method.', + line: 3, + column: 25 + }, + { + message: + 'Prefer method handler over inline handler in v-on. Note that you may need to create a new method.', + line: 4, + column: 25 + }, + { + message: + 'Prefer method handler over inline handler in v-on. Note that you may need to create a new method.', + line: 5, + column: 25 + }, + { + message: + 'Prefer method handler over inline handler in v-on. Note that you may need to create a new method.', + line: 6, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: null, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 2, + column: 33 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: null, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 5, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: null, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 4, + column: 25 + }, + { + message: 'Prefer method handler over inline handler in v-on.', + line: 5, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer method handler over inline handler in v-on.', + line: 3, + column: 27 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline-function']], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 3, + column: 27 + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 4, + column: 27 + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 5, + column: 27 + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 6, + column: 27 + } + ] + }, + // 'inline-function' + { + filename: 'test.vue', + code: ``, + output: ``, + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer inline function over inline handler in v-on.', + line: 3, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: '', + output: null, + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 24 + } + ] + }, + { + filename: 'test.vue', + code: '', + output: null, + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 23 + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 27 + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 27 + } + ] + }, + { + filename: 'test.vue', + code: '', + output: '', + options: ['inline-function'], + errors: [ + { + message: 'Prefer inline function over inline handler in v-on.', + line: 1, + column: 27 + } + ] + }, + // 'inline' with method + { + filename: 'test.vue', + code: ``, + output: null, + options: ['inline'], + errors: [ + { + message: 'Prefer inline handler over method handler in v-on.', + line: 2, + column: 25 + } + ] + }, + // ['method', 'inline'] + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline'], { ignoreIncludesComment: true }], + errors: [ + { + message: 'Prefer inline handler over inline function in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + `, + output: ` + `, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: + 'Prefer method handler over inline function in v-on. Note that you may need to create a new method.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: + 'Prefer method handler over inline function in v-on. Note that you may need to create a new method.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: + 'Prefer method handler over inline function in v-on. Note that you may need to create a new method.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 5, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + output: null, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 25 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 5, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer method handler over inline function in v-on.', + line: 3, + column: 27 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 4, + column: 27 + }, + { + message: 'Prefer method handler over inline function in v-on.', + line: 5, + column: 27 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: ``, + options: [['method', 'inline']], + errors: [ + { + message: 'Prefer inline handler over inline function in v-on.', + line: 3, + column: 27 + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 4, + column: 27 + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 5, + column: 27 + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 6, + column: 27 + } + ] + }, + // 'inline' with function + { + filename: 'test.vue', + code: ``, + output: ``, + options: ['inline'], + errors: [ + { + message: 'Prefer inline handler over inline function in v-on.', + line: 2, + column: 25 + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 3, + column: 25 + }, + { + message: 'Prefer inline handler over inline function in v-on.', + line: 4, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: ['inline'], + errors: [ + { + message: 'Prefer inline handler over inline function in v-on.', + line: 2, + column: 25 + } + ] + }, + { + filename: 'test.vue', + code: ``, + output: null, + options: ['inline'], + errors: [ + { + message: + 'Prefer inline handler over inline function in v-on. Note that the custom event must be changed to a single payload.', + line: 2, + column: 25 + } + ] + } + ] +}) diff --git a/tests/lib/rules/v-on-style.js b/tests/lib/rules/v-on-style.js index 7ffd115b9..820f98c4b 100644 --- a/tests/lib/rules/v-on-style.js +++ b/tests/lib/rules/v-on-style.js @@ -5,20 +5,11 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/v-on-style') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('v-on-style', rule, { @@ -55,16 +46,16 @@ tester.run('v-on-style', rule, { }, { filename: 'test.vue', - options: ['shorthand'], code: '', output: '', + options: ['shorthand'], errors: ["Expected '@' instead of 'v-on:'."] }, { filename: 'test.vue', - options: ['longform'], code: '', output: '', + options: ['longform'], errors: ["Expected 'v-on:' instead of '@'."] } ] diff --git a/tests/lib/rules/v-slot-style.js b/tests/lib/rules/v-slot-style.js index 72363f779..f35f1a9b8 100644 --- a/tests/lib/rules/v-slot-style.js +++ b/tests/lib/rules/v-slot-style.js @@ -4,20 +4,11 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/v-slot-style') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015 } + languageOptions: { parser: require('vue-eslint-parser'), ecmaVersion: 2015 } }) tester.run('v-slot-style', rule, { @@ -225,13 +216,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ atComponent: 'shorthand' }], errors: [ { messageId: 'expectedShorthand', data: { actual: 'v-slot:default', argument: 'default' } } - ], - options: [{ atComponent: 'shorthand' }] + ] }, { code: ` @@ -244,13 +235,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ atComponent: 'shorthand' }], errors: [ { messageId: 'expectedShorthand', data: { actual: 'v-slot', argument: 'default' } } - ], - options: [{ atComponent: 'shorthand' }] + ] }, { code: ` @@ -263,13 +254,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ atComponent: 'longform' }], errors: [ { messageId: 'expectedLongform', data: { actual: '#default', argument: 'default' } } - ], - options: [{ atComponent: 'longform' }] + ] }, { code: ` @@ -282,13 +273,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ atComponent: 'longform' }], errors: [ { messageId: 'expectedLongform', data: { actual: 'v-slot', argument: 'default' } } - ], - options: [{ atComponent: 'longform' }] + ] }, { @@ -350,13 +341,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ default: 'longform' }], errors: [ { messageId: 'expectedLongform', data: { actual: '#default', argument: 'default' } } - ], - options: [{ default: 'longform' }] + ] }, { code: ` @@ -373,13 +364,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ default: 'longform' }], errors: [ { messageId: 'expectedLongform', data: { actual: 'v-slot', argument: 'default' } } - ], - options: [{ default: 'longform' }] + ] }, { code: ` @@ -396,13 +387,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ default: 'v-slot' }], errors: [ { messageId: 'expectedVSlot', data: { actual: '#default', argument: 'default' } } - ], - options: [{ default: 'v-slot' }] + ] }, { code: ` @@ -419,13 +410,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ default: 'v-slot' }], errors: [ { messageId: 'expectedVSlot', data: { actual: 'v-slot:default', argument: 'default' } } - ], - options: [{ default: 'v-slot' }] + ] }, { @@ -465,13 +456,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ named: 'longform' }], errors: [ { messageId: 'expectedLongform', data: { actual: '#foo', argument: 'foo' } } - ], - options: [{ named: 'longform' }] + ] }, { @@ -511,13 +502,13 @@ tester.run('v-slot-style', rule, { `, + options: [{ named: 'longform' }], errors: [ { messageId: 'expectedLongform', data: { actual: '#[foo]', argument: '[foo]' } } - ], - options: [{ named: 'longform' }] + ] } ] }) diff --git a/tests/lib/rules/valid-attribute-name.js b/tests/lib/rules/valid-attribute-name.js new file mode 100644 index 000000000..bb94ca427 --- /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-compat').RuleTester +const rule = require('../../../lib/rules/valid-attribute-name') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + 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-define-emits.js b/tests/lib/rules/valid-define-emits.js index 557101d67..57a130299 100644 --- a/tests/lib/rules/valid-define-emits.js +++ b/tests/lib/rules/valid-define-emits.js @@ -4,20 +4,15 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/valid-define-emits') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015, sourceType: 'module' } + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2015, + sourceType: 'module' + } }) tester.run('valid-define-emits', rule, { @@ -48,7 +43,9 @@ tester.run('valid-define-emits', rule, { defineEmits<(e: 'notify')=>void>() `, - parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + } }, { filename: 'test.vue', @@ -77,9 +74,6 @@ tester.run('valid-define-emits', rule, { { // https://github.com/vuejs/eslint-plugin-vue/issues/1656 filename: 'test.vue', - parserOptions: { - parser: require.resolve('@typescript-eslint/parser') - }, code: ` - ` + `, + languageOptions: { + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } }, { filename: 'test.vue', - parserOptions: { - parser: require.resolve('@typescript-eslint/parser') - }, code: ` - ` + `, + languageOptions: { + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } + }, + { + filename: 'test.vue', + code: ` + ` } ], invalid: [ @@ -131,7 +142,7 @@ tester.run('valid-define-emits', rule, { `, errors: [ { - message: '`defineEmits` are referencing locally declared variables.', + message: '`defineEmits` is referencing locally declared variables.', line: 5 } ] @@ -144,7 +155,9 @@ tester.run('valid-define-emits', rule, { defineEmits<(e: 'notify')=>void>({ submit: null }) `, - parserOptions: { parser: require.resolve('@typescript-eslint/parser') }, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, errors: [ { message: '`defineEmits` has both a type-only emit and an argument.', diff --git a/tests/lib/rules/valid-define-options.js b/tests/lib/rules/valid-define-options.js new file mode 100644 index 000000000..867bcb569 --- /dev/null +++ b/tests/lib/rules/valid-define-options.js @@ -0,0 +1,210 @@ +/** + * @author Yosuke Ota + * See LICENSE file in root directory for full license. + */ +'use strict' + +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/valid-define-options') + +const tester = new RuleTester({ + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2015, + sourceType: 'module' + } +}) + +tester.run('valid-define-options', rule, { + valid: [ + { + filename: 'test.vue', + code: ` + + ` + }, + { + filename: 'test.vue', + code: ` + + + ` + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } + }, + { + filename: 'test.vue', + code: ` + ` + } + ], + invalid: [ + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: '`defineOptions` is referencing locally declared variables.', + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: '`defineOptions` has been called multiple times.', + line: 3 + }, + { + message: '`defineOptions` has been called multiple times.', + line: 4 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: 'Options are not defined.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, + errors: [ + { + message: 'Options are not defined.', + line: 3 + }, + { + message: '`defineOptions()` cannot accept type arguments.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `props`. Use `defineProps()` instead.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `emits`. Use `defineEmits()` instead.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `expose`. Use `defineExpose()` instead.', + line: 3 + } + ] + }, + { + filename: 'test.vue', + code: ` + + `, + errors: [ + { + message: + '`defineOptions()` cannot be used to declare `slots`. Use `defineSlots()` instead.', + line: 3 + } + ] + } + ] +}) diff --git a/tests/lib/rules/valid-define-props.js b/tests/lib/rules/valid-define-props.js index 10894e069..d20209846 100644 --- a/tests/lib/rules/valid-define-props.js +++ b/tests/lib/rules/valid-define-props.js @@ -4,20 +4,15 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/valid-define-props') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { ecmaVersion: 2015, sourceType: 'module' } + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2015, + sourceType: 'module' + } }) tester.run('valid-define-props', rule, { @@ -48,7 +43,9 @@ tester.run('valid-define-props', rule, { defineProps<{ msg?:string }>() `, - parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + } }, { filename: 'test.vue', @@ -80,9 +77,6 @@ tester.run('valid-define-props', rule, { { // https://github.com/vuejs/eslint-plugin-vue/issues/1656 filename: 'test.vue', - parserOptions: { - parser: require.resolve('@typescript-eslint/parser') - }, code: ` - ` + `, + languageOptions: { + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } }, { filename: 'test.vue', - parserOptions: { - parser: require.resolve('@typescript-eslint/parser') - }, code: ` - ` + `, + languageOptions: { + parserOptions: { + parser: require.resolve('@typescript-eslint/parser') + } + } + }, + { + filename: 'test.vue', + code: ` + ` } ], invalid: [ @@ -134,7 +145,7 @@ tester.run('valid-define-props', rule, { `, errors: [ { - message: '`defineProps` are referencing locally declared variables.', + message: '`defineProps` is referencing locally declared variables.', line: 5 } ] @@ -147,7 +158,9 @@ tester.run('valid-define-props', rule, { defineProps<{ msg?:string }>({ msg: String }) `, - parserOptions: { parser: require.resolve('@typescript-eslint/parser') }, + languageOptions: { + parserOptions: { parser: require.resolve('@typescript-eslint/parser') } + }, errors: [ { message: '`defineProps` has both a type-only props and an argument.', diff --git a/tests/lib/rules/no-invalid-model-keys.js b/tests/lib/rules/valid-model-definition.js similarity index 82% rename from tests/lib/rules/no-invalid-model-keys.js rename to tests/lib/rules/valid-model-definition.js index ebe4de973..ed6013247 100644 --- a/tests/lib/rules/no-invalid-model-keys.js +++ b/tests/lib/rules/valid-model-definition.js @@ -4,24 +4,16 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const rule = require('../../../lib/rules/no-invalid-model-keys') -const RuleTester = require('eslint').RuleTester - -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ +const rule = require('../../../lib/rules/valid-model-definition') +const RuleTester = require('../../eslint-compat').RuleTester const ruleTester = new RuleTester({ - parserOptions: { + languageOptions: { ecmaVersion: 2018, sourceType: 'module' } }) -ruleTester.run('no-invalid-model-keys', rule, { +ruleTester.run('valid-model-definition', rule, { valid: [ { filename: 'test.vue', diff --git a/tests/lib/rules/valid-next-tick.js b/tests/lib/rules/valid-next-tick.js index a2ac636fb..2e776e330 100644 --- a/tests/lib/rules/valid-next-tick.js +++ b/tests/lib/rules/valid-next-tick.js @@ -6,20 +6,12 @@ */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../eslint-compat').RuleTester const rule = require('../../../lib/rules/valid-next-tick') -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2017, sourceType: 'module' } @@ -129,6 +121,19 @@ tester.run('valid-next-tick', rule, { }, } }` + }, + + // https://github.com/vuejs/eslint-plugin-vue/issues/1776 + { + filename: 'test.vue', + code: `` } ], invalid: [ @@ -151,6 +156,7 @@ tester.run('valid-next-tick', rule, { column: 11, suggestions: [ { + messageId: 'addAwait', output: ` @@ -73,7 +54,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -89,7 +69,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -104,7 +83,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -119,7 +97,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -153,7 +129,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -161,7 +136,7 @@ describe('script-setup-uses-vars', () => { {{post}} `, - parserOptions: { + languageOptions: { ecmaVersion: 2022, sourceType: 'module' } @@ -172,7 +147,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -188,7 +162,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -206,7 +179,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -224,7 +196,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` + + + ` + } + ] + : null }, { message: "'baz' is assigned a value but never used.", - line: 19 + line: 18 } ] }, @@ -264,7 +266,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -275,7 +276,23 @@ describe('script-setup-uses-vars', () => { errors: [ { message: "'camelCase' is defined but never used.", - line: 4 + line: 3, + suggestions: semver.gte(ESLint.version, '9.17.0') + ? [ + { + desc: "Remove unused variable 'camelCase'.", + output: ` + + + + ` + } + ] + : null } ] }, @@ -285,7 +302,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` + + + ` + } + ] + : null } ] }, @@ -306,7 +340,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -318,7 +351,7 @@ describe('script-setup-uses-vars', () => { errors: [ { message: "'i' is assigned a value but never used.", - line: 4 + line: 3 } ] }, @@ -328,7 +361,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -339,7 +371,7 @@ describe('script-setup-uses-vars', () => { errors: [ { message: "'msg' is assigned a value but never used.", - line: 4 + line: 3 } ] }, @@ -349,7 +381,6 @@ describe('script-setup-uses-vars', () => { filename: 'test.vue', code: ` @@ -366,4 +397,45 @@ describe('script-setup-uses-vars', () => { } ] }) + + 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/comments.js b/tests/lib/utils/comments.js new file mode 100644 index 000000000..9e567f6b1 --- /dev/null +++ b/tests/lib/utils/comments.js @@ -0,0 +1,67 @@ +'use strict' + +const assert = require('assert') +const { + isBlockComment, + isJSDocComment +} = require('../../../lib/utils/comments.js') + +// //foo +const lineCommentNode = { + type: 'Line', + value: 'foo' +} + +// /*foo*/ +const blockCommentNodeWithoutAsterisks = { + type: 'Block', + value: 'foo' +} + +// //** foo */ +const blockCommentNodeWithOneAsterisk = { + type: 'Block', + value: '* foo' +} + +// /*** foo */ +const blockCommentNodeWithTwoAsterisks = { + type: 'Block', + value: '** foo' +} + +describe('isJSDocComment()', () => { + it('returns true for JSDoc comments', () => { + assert.equal(isJSDocComment(blockCommentNodeWithOneAsterisk), true) + }) + + it('returns false for block comments', () => { + assert.equal(isJSDocComment(blockCommentNodeWithoutAsterisks), false) + }) + + it('returns false for line comments', () => { + assert.equal(isJSDocComment(lineCommentNode), false) + }) + + it('returns false for block comments with two asterisks', () => { + assert.equal(isJSDocComment(blockCommentNodeWithTwoAsterisks), false) + }) +}) + +describe('isBlockComment()', () => { + it('returns false for JSDoc comments', () => { + assert.equal(isBlockComment(blockCommentNodeWithOneAsterisk), false) + }) + + it('returns true for block comments', () => { + assert.equal(isBlockComment(blockCommentNodeWithoutAsterisks), true) + }) + + it('returns false for line comments', () => { + assert.equal(isBlockComment(lineCommentNode), false) + }) + + it('returns true for block comments with two asterisks', () => { + assert.equal(isBlockComment(blockCommentNodeWithTwoAsterisks), true) + }) +}) diff --git a/tests/lib/utils/core-rules/wrap-core-rule.js b/tests/lib/utils/core-rules/wrap-core-rule.js index 912957aa6..b07b17e9e 100644 --- a/tests/lib/utils/core-rules/wrap-core-rule.js +++ b/tests/lib/utils/core-rules/wrap-core-rule.js @@ -1,22 +1,18 @@ 'use strict' -const RuleTester = require('eslint').RuleTester +const RuleTester = require('../../../eslint-compat').RuleTester const utils = require('../../../../lib/utils/index') const rule = utils.wrapCoreRule('foo') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2020, sourceType: 'module' } }) -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - tester.run('wrap-core-rule-with-unknown', rule, { valid: [ { diff --git a/tests/lib/utils/html-comments.js b/tests/lib/utils/html-comments.js index 066c73eb1..69992b09d 100644 --- a/tests/lib/utils/html-comments.js +++ b/tests/lib/utils/html-comments.js @@ -4,7 +4,7 @@ const fs = require('fs') const path = require('path') const assert = require('assert') -const Linter = require('eslint').Linter +const Linter = require('../../eslint-compat').Linter const htmlComments = require('../../../lib/utils/html-comments') @@ -37,17 +37,25 @@ function tokenize(code, option) { const linter = new Linter() const result = [] - linter.defineRule('vue/html-comments-test', (content) => - htmlComments.defineVisitor(content, option, (commentTokens) => { - result.push(commentTokens) - }) - ) - linter.defineParser('vue-eslint-parser', require('vue-eslint-parser')) linter.verify( code, { - parser: 'vue-eslint-parser', - parserOptions: { ecmaVersion: 2018 }, + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2018 + }, + plugins: { + vue: { + rules: { + 'html-comments-test': { + create: (content) => + htmlComments.defineVisitor(content, option, (commentTokens) => { + result.push(commentTokens) + }) + } + } + } + }, rules: { 'vue/html-comments-test': 'error' } }, undefined, diff --git a/tests/lib/utils/index.js b/tests/lib/utils/index.js index b4a052090..6afecaeaa 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,57 +110,42 @@ 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: { } }`) const parsed = utils.getStaticPropertyName(node.properties[0]) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) it('should parse property expression with literal', () => { const node = parse(`const test = { ['computed'] () {} }`) const parsed = utils.getStaticPropertyName(node.properties[0]) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) it('should parse property expression with template literal', () => { const node = parse(`const test = { [\`computed\`] () {} }`) const parsed = utils.getStaticPropertyName(node.properties[0]) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) }) 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'] () {} }`) const parsed = utils.getStringLiteralValue(node.properties[0].key) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) it('should parse template literal', () => { const node = parse(`const test = { [\`computed\`] () {} }`) const parsed = utils.getStringLiteralValue(node.properties[0].key) - assert.ok(parsed === 'computed') + assert.strictEqual(parsed, 'computed') }) }) 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() { @@ -388,27 +365,31 @@ describe('getComponentProps', () => { } }`) - assert.equal(props.length, 4, 'it detects all props') + assert.equal(props.length, 5, 'it detects all props') + + assert.strictEqual(props[0].key, undefined) + assert.strictEqual(props[0].node.type, 'SpreadElement') + assert.strictEqual(props[0].value, undefined) - assert.ok(props[0].key.type === 'Identifier') - assert.ok(props[0].node.type === 'Property') - assert.ok(props[0].value.type === 'Identifier') + assert.strictEqual(props[1].key.type, 'Identifier') + assert.strictEqual(props[1].node.type, 'Property') + assert.strictEqual(props[1].value.type, 'Identifier') - assert.ok(props[1].key.type === 'Identifier') - assert.ok(props[1].node.type === 'Property') - assert.ok(props[1].value.type === 'ObjectExpression') + assert.strictEqual(props[2].key.type, 'Identifier') + assert.strictEqual(props[2].node.type, 'Property') + assert.strictEqual(props[2].value.type, 'ObjectExpression') - assert.ok(props[2].key.type === 'Identifier') - assert.ok(props[2].node.type === 'Property') - assert.ok(props[2].value.type === 'ArrayExpression') + assert.strictEqual(props[3].key.type, 'Identifier') + assert.strictEqual(props[3].node.type, 'Property') + assert.strictEqual(props[3].value.type, 'ArrayExpression') - assert.deepEqual(props[3].key, props[3].value) - assert.ok(props[3].node.type === 'Property') - assert.ok(props[3].value.type === 'Identifier') + assert.deepEqual(props[4].key, props[4].value) + assert.strictEqual(props[4].node.type, 'Property') + assert.strictEqual(props[4].value.type, 'Identifier') }) it('should return computed from array props', () => { - const props = parse(`const test = { + const props = parseProps(`const test = { name: 'test', data() { return {} @@ -418,21 +399,21 @@ describe('getComponentProps', () => { assert.equal(props.length, 4, 'it detects all props') - assert.ok(props[0].node.type === 'Literal') + assert.strictEqual(props[0].node.type, 'Literal') assert.deepEqual(props[0].key, props[0].node) - assert.ok(!props[0].value) + assert.strictEqual(props[0].value, null) - assert.ok(props[1].node.type === 'Identifier') - assert.ok(!props[1].key) - assert.ok(!props[1].value) + assert.strictEqual(props[1].node.type, 'Identifier') + assert.strictEqual(props[1].key, null) + assert.strictEqual(props[1].value, null) - assert.ok(props[2].node.type === 'TemplateLiteral') + assert.strictEqual(props[2].node.type, 'TemplateLiteral') assert.deepEqual(props[2].key, props[2].node) - assert.ok(!props[2].value) + assert.strictEqual(props[2].value, null) - assert.ok(props[3].node.type === 'Literal') - assert.ok(!props[3].key) - assert.ok(!props[3].value) + assert.strictEqual(props[3].node.type, 'Literal') + assert.strictEqual(props[3].key, null) + assert.strictEqual(props[3].value, null) }) }) diff --git a/tests/lib/utils/ref-object-references.js b/tests/lib/utils/ref-object-references.js new file mode 100644 index 000000000..68c526b36 --- /dev/null +++ b/tests/lib/utils/ref-object-references.js @@ -0,0 +1,202 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const assert = require('assert') +const vueESLintParser = require('vue-eslint-parser') + +const Linter = require('../../eslint-compat').Linter + +const { + extractRefObjectReferences, + extractReactiveVariableReferences +} = require('../../../lib/utils/ref-object-references') + +const FIXTURE_ROOT = path.resolve( + __dirname, + '../../fixtures/utils/ref-object-references' +) +const REF_OBJECTS_FIXTURE_ROOT = path.resolve(FIXTURE_ROOT, 'ref-objects') +const REACTIVE_VARS_FIXTURE_ROOT = path.resolve(FIXTURE_ROOT, 'reactive-vars') + +/** + * @typedef {object} LoadedPattern + * @property {string} code The code to test. + * @property {string} name The name of the pattern. + * @property {string} sourceFilePath + * @property {string} resultFilePath + * @property {object} [options] + * @property {string} [options.parser] + */ +/** + * Load test patterns from fixtures. + * + * @returns {LoadedPattern[]} The loaded patterns. + */ +function loadPatterns(rootDir) { + return fs.readdirSync(rootDir).map((name) => { + for (const [sourceFile, resultFile, options] of [ + ['source.js', 'result.js'], + [ + 'source.vue', + 'result.vue', + { languageOptions: { parser: 'vue-eslint-parser' } } + ] + ]) { + const sourceFilePath = path.join(rootDir, name, sourceFile) + if (fs.existsSync(sourceFilePath)) { + return { + code: fs.readFileSync(sourceFilePath, 'utf8'), + name, + sourceFilePath, + resultFilePath: path.join(rootDir, name, resultFile), + options + } + } + } + }) +} + +function extractRefs(code, extract, options) { + const linter = new Linter() + const references = [] + + const messages = linter.verify( + code, + { + ...options, + plugins: { + vue: { + rules: { + 'extract-test': { + create: (context) => { + const refs = extract(context) + + const processed = new Set() + return { + '*'(node) { + if (processed.has(node)) { + // Old ESLint may be called twice on the same node. + return + } + processed.add(node) + const data = refs.get(node) + if (data) { + references.push(data) + } + } + } + } + } + } + } + }, + languageOptions: { + ...options?.languageOptions, + ...(options?.languageOptions?.parser === 'vue-eslint-parser' + ? { parser: vueESLintParser } + : {}), + ecmaVersion: 2020, + sourceType: 'module', + globals: { + $ref: 'readonly', + $computed: 'readonly', + $shallowRef: 'readonly', + $customRef: 'readonly', + $toRef: 'readonly', + $: 'readonly', + $$: 'readonly' + } + }, + rules: { 'vue/extract-test': 'error' } + }, + undefined, + true + ) + + const errors = messages.map((message) => message.message) + if (errors.length > 0) { + assert.fail(errors.join(',')) + } + + return references +} + +describe('extractRefObjectReferences()', () => { + for (const { code, sourceFilePath, resultFilePath, options } of loadPatterns( + REF_OBJECTS_FIXTURE_ROOT + )) { + describe(sourceFilePath, () => { + it('should to extract the references to match the expected references.', () => { + /** @type {import('../../../lib/utils/ref-object-references').RefObjectReference[]} */ + const references = [ + ...extractRefs(code, extractRefObjectReferences, options) + ] + + let result = '' + let start = 0 + let ref + while ((ref = references.shift())) { + result += code.slice(start, ref.node.range[0]) + result += `/*>*/` + result += code.slice(...ref.node.range) + result += `/*<${JSON.stringify({ + type: ref.type, + method: ref.method + })}*/` + start = ref.node.range[1] + } + result += code.slice(start) + + const actual = result + + if (!fs.existsSync(resultFilePath)) { + // update fixture + fs.writeFileSync(resultFilePath, actual, 'utf8') + } + + const expected = fs.readFileSync(resultFilePath, 'utf8') + assert.strictEqual(actual, expected) + }) + }) + } +}) +describe('extractReactiveVariableReferences()', () => { + for (const { code, sourceFilePath, resultFilePath, options } of loadPatterns( + REACTIVE_VARS_FIXTURE_ROOT + )) { + describe(sourceFilePath, () => { + it('should to extract the references to match the expected references.', () => { + /** @type {import('../../../lib/utils/ref-object-references').ReactiveVariableReference[]} */ + const references = [ + ...extractRefs(code, extractReactiveVariableReferences, options) + ] + + let result = '' + let start = 0 + let ref + while ((ref = references.shift())) { + result += code.slice(start, ref.node.range[0]) + result += `/*>*/` + result += code.slice(...ref.node.range) + result += `/*<${JSON.stringify({ + escape: ref.escape, + method: ref.method + })}*/` + start = ref.node.range[1] + } + result += code.slice(start) + + const actual = result + + if (!fs.existsSync(resultFilePath)) { + // update fixture + fs.writeFileSync(resultFilePath, actual, 'utf8') + } + + const expected = fs.readFileSync(resultFilePath, 'utf8') + assert.strictEqual(actual, expected) + }) + }) + } +}) diff --git a/tests/lib/utils/selector.js b/tests/lib/utils/selector.js new file mode 100644 index 000000000..b5b0ee986 --- /dev/null +++ b/tests/lib/utils/selector.js @@ -0,0 +1,99 @@ +'use strict' + +const fs = require('fs') +const path = require('path') +const assert = require('assert') + +const Linter = require('../../eslint-compat').Linter + +const selector = require('../../../lib/utils/selector') +const utils = require('../../../lib/utils') + +const FIXTURE_ROOT = path.resolve(__dirname, '../../fixtures/utils/selector') + +/** + * Load test patterns from fixtures. + * + * @returns {object} The loaded patterns. + */ +function loadPatterns() { + return fs.readdirSync(FIXTURE_ROOT).map((name) => { + const code0 = fs.readFileSync( + path.join(FIXTURE_ROOT, name, 'source.vue'), + 'utf8' + ) + const code = code0.replace(/^/, ``) + const inputSelector = /^/.exec(code0)[1].trim() + return { code, name, inputSelector } + }) +} + +function extractElements(code, inputSelector) { + const linter = new Linter() + const matches = [] + + const messages = linter.verify( + code, + { + plugins: { + vue: { + rules: { + 'selector-test': { + create: (context) => { + const parsed = selector.parseSelector(inputSelector, context) + return utils.defineDocumentVisitor(context, { + VElement(node) { + if (parsed.test(node)) { + matches.push( + context + .getSourceCode() + .text.slice(...node.startTag.range) + ) + } + } + }) + } + } + } + } + }, + languageOptions: { + parser: require('vue-eslint-parser'), + ecmaVersion: 2018 + }, + rules: { 'vue/selector-test': 'error' } + }, + undefined, + true + ) + + return { + selector: inputSelector, + matches, + errors: messages.map((message) => message.message) + } +} + +describe('parseSelector()', () => { + for (const { name, code, inputSelector } of loadPatterns()) { + describe(`'test/fixtures/utils/selector/${name}/source.vue'`, () => { + it('should to parse the selector to match the valid elements.', () => { + const elements = extractElements(code, inputSelector) + const actual = JSON.stringify(elements, null, 4) + + // update fixture + // fs.writeFileSync( + // path.join(FIXTURE_ROOT, name, 'result.json'), + // actual, + // 'utf8' + // ) + + const expected = fs.readFileSync( + path.join(FIXTURE_ROOT, name, 'result.json'), + 'utf8' + ) + assert.strictEqual(actual, expected) + }) + }) + } +}) diff --git a/tests/lib/utils/ts-utils/index/get-component-emits.js b/tests/lib/utils/ts-utils/index/get-component-emits.js new file mode 100644 index 000000000..8c9d5d34c --- /dev/null +++ b/tests/lib/utils/ts-utils/index/get-component-emits.js @@ -0,0 +1,127 @@ +/** + * Test for getComponentEmitsFromTypeDefineTypes + */ +'use strict' + +const path = require('path') +const fs = require('fs') +const Linter = require('../../../../eslint-compat').Linter +const parser = require('vue-eslint-parser') +const tsParser = require('@typescript-eslint/parser') +const utils = require('../../../../../lib/utils/index') +const assert = require('assert') + +const FIXTURES_ROOT = path.resolve( + __dirname, + '../../../../fixtures/utils/ts-utils' +) +const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json') +const SRC_TS_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.ts') + +function extractComponentProps(code, tsFileCode) { + const linter = new Linter() + const result = [] + const config = { + files: ['**/*.vue'], + languageOptions: { + parser, + ecmaVersion: 2020, + parserOptions: { + parser: tsParser, + project: [TSCONFIG_PATH], + extraFileExtensions: ['.vue'] + } + }, + plugins: { + test: { + rules: { + test: { + create(context) { + return utils.defineScriptSetupVisitor(context, { + onDefineEmitsEnter(_node, emits) { + result.push( + ...emits.map((emit) => ({ + type: emit.type, + name: emit.emitName + })) + ) + } + }) + } + } + } + } + }, + rules: { + 'test/test': 'error' + } + } + fs.writeFileSync(SRC_TS_TEST_PATH, tsFileCode || '', 'utf8') + // clean './src/test.ts' cache + tsParser.clearCaches() + assert.deepStrictEqual( + linter.verify(code, config, path.join(FIXTURES_ROOT, './src/test.vue')), + [] + ) + // reset + fs.writeFileSync(SRC_TS_TEST_PATH, '', 'utf8') + return result +} + +describe('getComponentEmitsFromTypeDefineTypes', () => { + for (const { scriptCode, tsFileCode, props: expected } of [ + { + scriptCode: `defineEmits<{(e:'foo'):void,(e:'bar'):void}>()`, + props: [ + { type: 'type', name: 'foo' }, + { type: 'type', name: 'bar' } + ] + }, + { + tsFileCode: `export type Emits = {(e:'foo'):void,(e:'bar'):void}`, + scriptCode: `import { Emits } from './test' + defineEmits()`, + props: [ + { type: 'infer-type', name: 'foo' }, + { type: 'infer-type', name: 'bar' } + ] + }, + { + tsFileCode: `export type Emits = any`, + scriptCode: `import { Emits } from './test' + defineEmits()`, + props: [{ type: 'unknown', name: null }] + }, + { + tsFileCode: `export type Emits = {(e:'foo' | 'bar'): void, (e:'baz',payload:number): void}`, + scriptCode: `import { Emits } from './test' + defineEmits()`, + props: [ + { type: 'infer-type', name: 'foo' }, + { type: 'infer-type', name: 'bar' }, + { type: 'infer-type', name: 'baz' } + ] + }, + { + tsFileCode: `export type Emits = { a: [], b: [number], c: [string]}`, + scriptCode: `import { Emits } from './test' + defineEmits()`, + props: [ + { type: 'infer-type', name: 'a' }, + { type: 'infer-type', name: 'b' }, + { type: 'infer-type', name: 'c' } + ] + } + ]) { + const code = `` + it(`should return expected props with :${code}`, () => { + const props = extractComponentProps(code, tsFileCode) + + assert.deepStrictEqual( + props, + expected, + `\n${JSON.stringify(props)}\n === \n${JSON.stringify(expected)}` + ) + }) + } +}) diff --git a/tests/lib/utils/ts-utils/index/get-component-props.js b/tests/lib/utils/ts-utils/index/get-component-props.js new file mode 100644 index 000000000..100d0a18c --- /dev/null +++ b/tests/lib/utils/ts-utils/index/get-component-props.js @@ -0,0 +1,218 @@ +/** + * Test for getComponentPropsFromTypeDefineTypes + */ +'use strict' + +const path = require('path') +const fs = require('fs') +const Linter = require('../../../../eslint-compat').Linter +const parser = require('vue-eslint-parser') +const tsParser = require('@typescript-eslint/parser') +const utils = require('../../../../../lib/utils/index') +const assert = require('assert') + +const FIXTURES_ROOT = path.resolve( + __dirname, + '../../../../fixtures/utils/ts-utils' +) +const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json') +const SRC_TS_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.ts') + +function extractComponentProps(code, tsFileCode) { + const linter = new Linter() + const result = [] + const config = { + files: ['**/*.vue'], + languageOptions: { + parser, + ecmaVersion: 2020, + parserOptions: { + parser: tsParser, + project: [TSCONFIG_PATH], + extraFileExtensions: ['.vue'] + } + }, + plugins: { + test: { + rules: { + test: { + create(context) { + return utils.defineScriptSetupVisitor(context, { + onDefinePropsEnter(_node, props) { + result.push( + ...props.map((prop) => ({ + type: prop.type, + name: prop.propName, + required: prop.required ?? null, + types: prop.types ?? null + })) + ) + } + }) + } + } + } + } + }, + rules: { + 'test/test': 'error' + } + } + fs.writeFileSync(SRC_TS_TEST_PATH, tsFileCode || '', 'utf8') + // clean './src/test.ts' cache + tsParser.clearCaches() + assert.deepStrictEqual( + linter.verify(code, config, path.join(FIXTURES_ROOT, './src/test.vue')), + [] + ) + // reset + fs.writeFileSync(SRC_TS_TEST_PATH, '', 'utf8') + return result +} + +describe('getComponentPropsFromTypeDefineTypes', () => { + for (const { scriptCode, tsFileCode, props: expected } of [ + { + scriptCode: `defineProps<{foo:string,bar?:number}>()`, + props: [ + { type: 'type', name: 'foo', required: true, types: ['String'] }, + { type: 'type', name: 'bar', required: false, types: ['Number'] } + ] + }, + { + scriptCode: `defineProps<{foo:string,bar?:number} & {baz?:string|number}>()`, + props: [ + { type: 'type', name: 'foo', required: true, types: ['String'] }, + { type: 'type', name: 'bar', required: false, types: ['Number'] }, + { + type: 'type', + name: 'baz', + required: false, + types: ['String', 'Number'] + } + ] + }, + { + tsFileCode: `export type Props = {foo:string,bar?:number}`, + scriptCode: `import { Props } from './test' + defineProps()`, + props: [ + { type: 'infer-type', name: 'foo', required: true, types: ['String'] }, + { type: 'infer-type', name: 'bar', required: false, types: ['Number'] } + ] + }, + { + tsFileCode: `export type Props = any`, + scriptCode: `import { Props } from './test' + defineProps()`, + props: [{ type: 'unknown', name: null, required: null, types: null }] + }, + { + tsFileCode: ` + interface Props { + a?: number; + b?: string; + } + export interface Props2 extends Required { + c?: boolean; + }`, + scriptCode: `import { Props2 } from './test' + defineProps()`, + props: [ + { type: 'infer-type', name: 'c', required: false, types: ['Boolean'] }, + { type: 'infer-type', name: 'a', required: true, types: ['Number'] }, + { type: 'infer-type', name: 'b', required: true, types: ['String'] } + ] + }, + { + tsFileCode: ` + export type Props = { + a: string + b?: number + c?: boolean + d?: boolean + e?: number | string + f?: () => number + g?: { foo?: string } + h?: string[] + i?: readonly string[] + }`, + scriptCode: `import { Props } from './test' + defineProps()`, + props: [ + { type: 'infer-type', name: 'a', required: true, types: ['String'] }, + { type: 'infer-type', name: 'b', required: false, types: ['Number'] }, + { type: 'infer-type', name: 'c', required: false, types: ['Boolean'] }, + { type: 'infer-type', name: 'd', required: false, types: ['Boolean'] }, + { + type: 'infer-type', + name: 'e', + required: false, + types: ['String', 'Number'] + }, + { type: 'infer-type', name: 'f', required: false, types: ['Function'] }, + { type: 'infer-type', name: 'g', required: false, types: ['Object'] }, + { type: 'infer-type', name: 'h', required: false, types: ['Array'] }, + { type: 'infer-type', name: 'i', required: false, types: ['Array'] } + ] + }, + { + tsFileCode: ` + export interface Props { + a?: number; + b?: string; + }`, + scriptCode: `import { Props } from './test' +defineProps()`, + props: [ + { type: 'infer-type', name: 'a', required: false, types: ['Number'] }, + { type: 'infer-type', name: 'b', required: false, types: ['String'] }, + { type: 'type', name: 'foo', required: false, types: ['String'] } + ] + }, + { + tsFileCode: ` + export type A = string | number`, + scriptCode: `import { A } from './test' +defineProps<{foo?:A}>()`, + props: [ + { + type: 'type', + name: 'foo', + required: false, + types: ['String', 'Number'] + } + ] + }, + { + scriptCode: `enum A {a = 'a', b = 'b'} +defineProps<{foo?:A}>()`, + props: [{ type: 'type', name: 'foo', required: false, types: ['String'] }] + }, + { + scriptCode: ` +const foo = 42 +enum A {a = foo, b = 'b'} +defineProps<{foo?:A}>()`, + props: [ + { + type: 'type', + name: 'foo', + required: false, + types: ['Number', 'String'] + } + ] + } + ]) { + const code = `` + it(`should return expected props with :${code}`, () => { + const props = extractComponentProps(code, tsFileCode) + + assert.deepStrictEqual( + props, + expected, + `\n${JSON.stringify(props)}\n === \n${JSON.stringify(expected)}` + ) + }) + } +}) diff --git a/tests/lib/utils/ts-utils/index/get-component-slots.js b/tests/lib/utils/ts-utils/index/get-component-slots.js new file mode 100644 index 000000000..410021b93 --- /dev/null +++ b/tests/lib/utils/ts-utils/index/get-component-slots.js @@ -0,0 +1,115 @@ +/** + * Test for getComponentSlotsFromTypeDefineTypes + */ +'use strict' + +const path = require('path') +const fs = require('fs') +const Linter = require('../../../../eslint-compat').Linter +const parser = require('vue-eslint-parser') +const tsParser = require('@typescript-eslint/parser') +const utils = require('../../../../../lib/utils/index') +const assert = require('assert') + +const FIXTURES_ROOT = path.resolve( + __dirname, + '../../../../fixtures/utils/ts-utils' +) +const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json') +const SRC_TS_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.ts') + +function extractComponentSlots(code, tsFileCode) { + const linter = new Linter() + const result = [] + const config = { + files: ['**/*.vue'], + languageOptions: { + parser, + ecmaVersion: 2020, + parserOptions: { + parser: tsParser, + project: [TSCONFIG_PATH], + extraFileExtensions: ['.vue'] + } + }, + plugins: { + test: { + rules: { + test: { + create(context) { + return utils.defineScriptSetupVisitor(context, { + onDefineSlotsEnter(_node, slots) { + result.push( + ...slots.map((prop) => ({ + type: prop.type, + name: prop.slotName + })) + ) + } + }) + } + } + } + } + }, + rules: { + 'test/test': 'error' + } + } + fs.writeFileSync(SRC_TS_TEST_PATH, tsFileCode || '', 'utf8') + // clean './src/test.ts' cache + tsParser.clearCaches() + assert.deepStrictEqual( + linter.verify(code, config, path.join(FIXTURES_ROOT, './src/test.vue')), + [] + ) + // reset + fs.writeFileSync(SRC_TS_TEST_PATH, '', 'utf8') + return result +} + +describe('getComponentSlotsFromTypeDefineTypes', () => { + for (const { scriptCode, tsFileCode, slots: expected } of [ + { + scriptCode: ` + defineSlots<{ + default(props: { msg: string }): any + }>() + `, + slots: [{ type: 'type', name: 'default' }] + }, + { + scriptCode: ` + interface Slots { + default(props: { msg: string }): any + } + defineSlots() + `, + slots: [{ type: 'type', name: 'default' }] + }, + { + scriptCode: ` + type Slots = { + default(props: { msg: string }): any + } + defineSlots() + `, + slots: [{ type: 'type', name: 'default' }] + } + ]) { + const code = ` + + ` + it(`should return expected slots with :${code}`, () => { + const slots = extractComponentSlots(code, tsFileCode) + + assert.deepStrictEqual( + slots, + expected, + `\n${JSON.stringify(slots)}\n === \n${JSON.stringify(expected)}` + ) + }) + } +}) diff --git a/tests/lib/utils/vue-component.js b/tests/lib/utils/vue-component.js index 91da3d28c..12fcd904f 100644 --- a/tests/lib/utils/vue-component.js +++ b/tests/lib/utils/vue-component.js @@ -5,10 +5,6 @@ const utils = require('../../../lib/utils/index') -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - const rule = { create(context) { return utils.executeOnVueComponent(context, (obj) => { @@ -24,8 +20,8 @@ const rule = { } } -const RuleTester = require('eslint').RuleTester -const parserOptions = { +const RuleTester = require('../../eslint-compat').RuleTester +const languageOptions = { ecmaVersion: 6, sourceType: 'module' } @@ -43,77 +39,83 @@ function validTests(ext) { { filename: `test.${ext}`, code: `export const foo = {}`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `export var foo = {}`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `const foo = {}`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `var foo = {}`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `let foo = {}`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `foo({ })`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `foo(() => { return {} })`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `Vue.component('async-example', function (resolve, reject) { })`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `Vue.component('async-example', function (resolve, reject) { resolve({}) })`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `new Vue({ })`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `{ foo: {} }`, - parserOptions + languageOptions }, { filename: `test.${ext}`, code: `export default (Foo as FooConstructor).extend({})`, - parser: require.resolve('@typescript-eslint/parser'), - parserOptions + languageOptions: { + ...languageOptions, + parser: require('@typescript-eslint/parser') + } }, { filename: `test.${ext}`, code: `export default Foo.extend({})`, - parser: require.resolve('@typescript-eslint/parser'), - parserOptions + languageOptions: { + ...languageOptions, + parser: require('@typescript-eslint/parser') + } }, { filename: `test.${ext}`, code: `export default Foo.extend({} as ComponentOptions)`, - parser: require.resolve('@typescript-eslint/parser'), - parserOptions + languageOptions: { + ...languageOptions, + parser: require('@typescript-eslint/parser') + } } ] } @@ -129,64 +131,70 @@ function invalidTests(ext) { }) // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(4)] }, { filename: `test.${ext}`, code: `Vue.component({})`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `Vue.mixin({})`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `Vue.extend({})`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `app.component('name', {})`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `app.mixin({})`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `export default (Vue as VueConstructor).extend({})`, - parser: require.resolve('@typescript-eslint/parser'), - parserOptions, + languageOptions: { + ...languageOptions, + parser: require('@typescript-eslint/parser') + }, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `export default Vue.extend({})`, - parser: require.resolve('@typescript-eslint/parser'), - parserOptions, + languageOptions: { + ...languageOptions, + parser: require('@typescript-eslint/parser') + }, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `export default Vue.extend({} as ComponentOptions)`, - parser: require.resolve('@typescript-eslint/parser'), - parserOptions, + languageOptions: { + ...languageOptions, + parser: require('@typescript-eslint/parser') + }, errors: [makeError(1)] }, { filename: `test.${ext}`, code: `createApp({})`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { @@ -196,7 +204,7 @@ function invalidTests(ext) { export default { } // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(3)] }, { @@ -206,7 +214,7 @@ function invalidTests(ext) { export default { } // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(3)] }, { @@ -219,7 +227,7 @@ function invalidTests(ext) { export default { } // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(6)] }, { @@ -231,7 +239,7 @@ function invalidTests(ext) { export var a = { } // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(3), makeError(5)] }, { @@ -243,7 +251,7 @@ function invalidTests(ext) { export default { } // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(3), makeError(5)] }, { @@ -254,8 +262,8 @@ function invalidTests(ext) { export let foo = { } // ${ext} `, - parserOptions, - errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(4)]) + languageOptions, + errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(4)] }, { filename: `test.${ext}`, @@ -265,7 +273,7 @@ function invalidTests(ext) { export let bar = { } // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(4)] }, { @@ -277,7 +285,7 @@ function invalidTests(ext) { bar({ }) // ${ext} `, - parserOptions, + languageOptions, errors: [makeError(4)] }, { @@ -292,8 +300,8 @@ function invalidTests(ext) { bar({ }) // ${ext} `, - parserOptions, - errors: (ext === 'js' ? [] : [makeError(3)]).concat([makeError(6)]) + languageOptions, + errors: [...(ext === 'js' ? [] : [makeError(3)]), makeError(6)] }, { filename: `test.${ext}`, @@ -309,49 +317,53 @@ function invalidTests(ext) { } // ${ext} `, - parserOptions, - errors: (ext === 'js' ? [] : [makeError(2)]).concat([makeError(8)]) + languageOptions, + errors: [...(ext === 'js' ? [] : [makeError(2)]), makeError(8)] }, { filename: `test.${ext}`, code: `export default defineComponent({})`, - parserOptions, + languageOptions, + errors: [makeError(1)] + }, + { + filename: `test.${ext}`, + code: `export default defineNuxtComponent({})`, + languageOptions, errors: [makeError(1)] } ] } -// ------------------------------------------------------------------------------ -// Tests -// ------------------------------------------------------------------------------ - const ruleTester = new RuleTester() ruleTester.run('vue-component', rule, { valid: [ { filename: 'test.js', code: `export default { }`, - parserOptions - } - ] - .concat(validTests('js')) - .concat(validTests('jsx')) - .concat(validTests('vue')), + languageOptions + }, + ...validTests('js'), + ...validTests('jsx'), + ...validTests('tsx'), + ...validTests('vue') + ], invalid: [ { filename: 'test.vue', code: `export default { }`, - parserOptions, + languageOptions, errors: [makeError(1)] }, { filename: 'test.jsx', code: `export default { }`, - parserOptions, + languageOptions, errors: [makeError(1)] - } + }, + ...invalidTests('js'), + ...invalidTests('jsx'), + ...invalidTests('tsx'), + ...invalidTests('vue') ] - .concat(invalidTests('js')) - .concat(invalidTests('jsx')) - .concat(invalidTests('vue')) }) diff --git a/tests/test-utils/typescript.js b/tests/test-utils/typescript.js new file mode 100644 index 000000000..0c6bbd437 --- /dev/null +++ b/tests/test-utils/typescript.js @@ -0,0 +1,28 @@ +const path = require('path') +const tsParser = require('@typescript-eslint/parser') + +const FIXTURES_ROOT = path.resolve(__dirname, '../fixtures/typescript') +const TSCONFIG_PATH = path.resolve(FIXTURES_ROOT, './tsconfig.json') +const SRC_VUE_TEST_PATH = path.join(FIXTURES_ROOT, './src/test.vue') + +module.exports = { + getTypeScriptFixtureTestOptions +} + +function getTypeScriptFixtureTestOptions() { + const parser = require('vue-eslint-parser') + const languageOptions = { + parser, + ecmaVersion: 2020, + sourceType: 'module', + parserOptions: { + parser: { ts: tsParser }, + project: [TSCONFIG_PATH], + extraFileExtensions: ['.vue'] + } + } + return { + languageOptions, + filename: SRC_VUE_TEST_PATH + } +} diff --git a/tools/lib/categories.js b/tools/lib/categories.js index 7223fd87b..b0a01aa7d 100644 --- a/tools/lib/categories.js +++ b/tools/lib/categories.js @@ -9,48 +9,31 @@ const rules = require('./rules') const categoryTitles = { base: { - text: 'Base Rules (Enabling Correct ESLint Parsing)', - vuepress: 'Base Rules (Enabling Correct ESLint Parsing)' + text: 'Base Rules (Enabling Correct ESLint Parsing)' }, 'vue3-essential': { - text: 'Priority A: Essential (Error Prevention) for Vue.js 3.x', - vuepress: - 'Priority A: Essential (Error Prevention) for Vue.js 3.x' + text: 'Priority A: Essential (Error Prevention) for Vue.js 3.x' }, 'vue3-strongly-recommended': { - text: 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 3.x', - vuepress: - 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 3.x' + text: 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 3.x' }, 'vue3-recommended': { - text: 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 3.x', - vuepress: - 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 3.x' + text: 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 3.x' }, 'vue3-use-with-caution': { - text: 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 3.x', - vuepress: - 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 3.x' + text: 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 3.x' }, - essential: { - text: 'Priority A: Essential (Error Prevention) for Vue.js 2.x', - vuepress: - 'Priority A: Essential (Error Prevention) for Vue.js 2.x' + 'vue2-essential': { + text: 'Priority A: Essential (Error Prevention) for Vue.js 2.x' }, - 'strongly-recommended': { - text: 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 2.x', - vuepress: - 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 2.x' + 'vue2-strongly-recommended': { + text: 'Priority B: Strongly Recommended (Improving Readability) for Vue.js 2.x' }, - recommended: { - text: 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 2.x', - vuepress: - 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 2.x' + 'vue2-recommended': { + text: 'Priority C: Recommended (Minimizing Arbitrary Choices and Cognitive Overhead) for Vue.js 2.x' }, - 'use-with-caution': { - text: 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 2.x', - vuepress: - 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 2.x' + 'vue2-use-with-caution': { + text: 'Priority D: Use with Caution (Potentially Dangerous Patterns) for Vue.js 2.x' } } const categoryIds = Object.keys(categoryTitles) @@ -69,12 +52,38 @@ for (const rule of rules) { } } -module.exports = categoryIds - .map((categoryId) => ({ - categoryId, - title: categoryTitles[categoryId], - rules: (categoryRules[categoryId] || []).filter( - (rule) => !rule.meta.deprecated - ) - })) - .filter((category) => category.rules.length >= 1) +const CONFIG_NAME_CAPTIONS = { + base: ['"plugin:vue/base"', '*.configs["flat/base"]'], + 'vue3-essential': ['"plugin:vue/essential"', '*.configs["flat/essential"]'], + 'vue2-essential': [ + '"plugin:vue/vue2-essential"', + '*.configs["flat/vue2-essential"]' + ], + 'vue3-strongly-recommended': [ + '"plugin:vue/strongly-recommended"', + '*.configs["flat/strongly-recommended"]' + ], + 'vue2-strongly-recommended': [ + '"plugin:vue/vue2-strongly-recommended"', + '*.configs["flat/vue2-strongly-recommended"]' + ], + 'vue3-recommended': [ + '"plugin:vue/recommended"', + '*.configs["flat/recommended"]' + ], + 'vue2-recommended': [ + '"plugin:vue/vue2-recommended"', + '*.configs["flat/vue2-recommended"]' + ] +} + +module.exports = { + CONFIG_NAME_CAPTIONS, + categories: categoryIds + .map((categoryId) => ({ + categoryId, + title: categoryTitles[categoryId], + rules: categoryRules[categoryId] || [] + })) + .filter((category) => category.rules.length > 0) +} diff --git a/tools/lib/configs.js b/tools/lib/configs.js deleted file mode 100644 index 031f41e67..000000000 --- a/tools/lib/configs.js +++ /dev/null @@ -1,15 +0,0 @@ -/** - * @author Michał Sajnóg - * See LICENSE file in root directory for full license. - */ - -'use strict' - -const fs = require('fs') -const path = require('path') -const ROOT = path.resolve(__dirname, '../../lib/configs') - -module.exports = fs - .readdirSync(ROOT) - .filter((file) => path.extname(file) === '.js') - .map((file) => path.basename(file, '.js')) diff --git a/tools/lib/http.js b/tools/lib/http.js new file mode 100644 index 000000000..27ff970f4 --- /dev/null +++ b/tools/lib/http.js @@ -0,0 +1,6 @@ +module.exports = { + httpGet +} +function httpGet(url) { + return fetch(url).then((res) => res.text()) +} diff --git a/tools/lib/utils.js b/tools/lib/utils.js new file mode 100644 index 000000000..937288ec4 --- /dev/null +++ b/tools/lib/utils.js @@ -0,0 +1,42 @@ +module.exports = { getPresetIds, formatItems } + +const presetCategories = { + base: null, + 'vue2-essential': 'base', + 'vue3-essential': 'base', + 'vue2-strongly-recommended': 'vue2-essential', + 'vue3-strongly-recommended': 'vue3-essential', + 'vue2-recommended': 'vue2-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/new-rule.js b/tools/new-rule.js index 3009dfdd3..d9a155795 100644 --- a/tools/new-rule.js +++ b/tools/new-rule.js @@ -4,46 +4,32 @@ const cp = require('child_process') const logger = console // main -;((ruleId) => { - if (ruleId == null) { - logger.error('Usage: npm run new ') +;((ruleName, authorName) => { + if (!ruleName || !authorName) { + logger.error('Usage: npm run new ') process.exitCode = 1 return } - if (!/^[\w-]+$/u.test(ruleId)) { - logger.error("Invalid RuleID '%s'.", ruleId) + if (!/^[\w-]+$/u.test(ruleName)) { + logger.error("Invalid rule name '%s'.", ruleName) process.exitCode = 1 return } - const ruleFile = path.resolve(__dirname, `../lib/rules/${ruleId}.js`) - const testFile = path.resolve(__dirname, `../tests/lib/rules/${ruleId}.js`) - const docFile = path.resolve(__dirname, `../docs/rules/${ruleId}.md`) + const ruleFile = path.resolve(__dirname, `../lib/rules/${ruleName}.js`) + const testFile = path.resolve(__dirname, `../tests/lib/rules/${ruleName}.js`) + const docFile = path.resolve(__dirname, `../docs/rules/${ruleName}.md`) fs.writeFileSync( ruleFile, `/** - * @author *****your name***** + * @author ${authorName} * See LICENSE file in root directory for full license. */ 'use strict' -// ------------------------------------------------------------------------------ -// Requirements -// ------------------------------------------------------------------------------ - const utils = require('../utils') -// ------------------------------------------------------------------------------ -// Helpers -// ------------------------------------------------------------------------------ - -// ... - -// ------------------------------------------------------------------------------ -// Rule Definition -// ------------------------------------------------------------------------------ - module.exports = { meta: { type: 'problem', @@ -72,23 +58,23 @@ module.exports = { fs.writeFileSync( testFile, `/** - * @author *****your name***** + * @author ${authorName} * See LICENSE file in root directory for full license. */ 'use strict' -const RuleTester = require('eslint').RuleTester -const rule = require('../../../lib/rules/${ruleId}') +const RuleTester = require('../../eslint-compat').RuleTester +const rule = require('../../../lib/rules/${ruleName}') const tester = new RuleTester({ - parser: require.resolve('vue-eslint-parser'), - parserOptions: { + languageOptions: { + parser: require('vue-eslint-parser'), ecmaVersion: 2020, sourceType: 'module' } }) -tester.run('${ruleId}', rule, { +tester.run('${ruleName}', rule, { valid: [ { filename: 'test.vue', @@ -124,20 +110,20 @@ tester.run('${ruleId}', rule, { `--- pageClass: rule-details sidebarDepth: 0 -title: vue/${ruleId} +title: vue/${ruleName} description: xxx --- -# vue/${ruleId} +# vue/${ruleName} > xxx -- :exclamation: ***This rule has not been released yet.*** +- :exclamation: _**This rule has not been released yet.**_ ## :book: Rule Details This rule .... - + \`\`\`vue