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/great-ravens-happen.md b/.changeset/great-ravens-happen.md new file mode 100644 index 000000000..eea070fd5 --- /dev/null +++ b/.changeset/great-ravens-happen.md @@ -0,0 +1,5 @@ +--- +"eslint-plugin-vue": minor +--- + +Added [`@stylistic/eslint-plugin`](https://eslint.style/) as optional peer dependency diff --git a/.changeset/purple-lights-invite.md b/.changeset/purple-lights-invite.md new file mode 100644 index 000000000..80aa90b8d --- /dev/null +++ b/.changeset/purple-lights-invite.md @@ -0,0 +1,5 @@ +--- +'eslint-plugin-vue': minor +--- + +Added `ignorePattern` option to [`vue/no-v-html`](https://eslint.vuejs.org/rules/no-v-html.html) diff --git a/.circleci/config.yml b/.circleci/config.yml deleted file mode 100644 index 456376308..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 - - 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 cc2e88ff0..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:** @@ -51,4 +52,9 @@ about: Create a report to help us improve --> **Repository to reproduce this issue** + 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..fede1a47a --- /dev/null +++ b/.github/workflows/CI.yml @@ -0,0 +1,110 @@ +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/*'] + 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 + - name: Test + run: npm test + + test-with-eslint-v8: + name: Test with ESLint v8 + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + - name: Install Node.js v18 + uses: actions/setup-node@v4 + with: + node-version: 18 + - name: Install Packages + run: npm install + - name: Install ESLint v8 + run: npm install --save-dev eslint@8 --force + - 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 + - name: Uninstall @stylistic/eslint-plugin + run: npm uninstall @stylistic/eslint-plugin + - name: Test + run: npm test + + test-with-old-eslint-stylistic: + name: Test with old ESLint Stylistic + strategy: + matrix: + stylistic: [2, 3, 4] + 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 + - name: Install @stylistic/eslint-plugin v${{ matrix.stylistic }} + run: npm install -D @stylistic/eslint-plugin@${{ matrix.stylistic }} --force + - name: Test + run: npm test + + test-with-typescript-eslint-v7: + name: Test with typescript-eslint v7 + 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 + - name: Install @typescript-eslint/parser v7 + run: npm install -D @typescript-eslint/parser@7 --force + - name: Test + run: npm test diff --git a/.github/workflows/Release.yml b/.github/workflows/Release.yml new file mode 100644 index 000000000..3653780d5 --- /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 + + - 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..d6fadf92c 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,10 @@ /test.* yarn.lock yarn-error.log -docs/.vuepress/dist +/docs/.vitepress/dist +/docs/.vitepress/build-system/shim/vue-eslint-parser.mjs +/docs/.vitepress/build-system/shim/@typescript-eslint/parser.mjs +/docs/.vitepress/.temp +/docs/.vitepress/cache typings/eslint/lib/rules +eslint-typegen.d.ts 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..171c80066 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -5,12 +5,17 @@ "type": "node", "request": "launch", "name": "Start testing", - "program": "${workspaceFolder}/node_modules/.bin/mocha", - "args": [ - "${file}", - "--watch" - ], + "program": "${workspaceFolder}/node_modules/.bin/vitest", + "args": ["run", "${file}", "--reporter=verbose"], + "console": "integratedTerminal" + }, + { + "type": "node", + "request": "launch", + "name": "Start testing (watch)", + "program": "${workspaceFolder}/node_modules/.bin/vitest", + "args": ["${file}", "--reporter=verbose"], "console": "integratedTerminal" } ] -} +} \ No newline at end of file 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..fa7a29427 --- /dev/null +++ b/CHANGELOG.md @@ -0,0 +1,45 @@ +# eslint-plugin-vue + +## 10.4.0 + +### Minor Changes + +- Added `ignoreParents` option to [`vue/no-deprecated-slot-attribute`](https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html) ([#2784](https://github.com/vuejs/eslint-plugin-vue/pull/2784)) + +- Added new [`vue/no-negated-v-if-condition`](https://eslint.vuejs.org/rules/no-negated-v-if-condition.html) rule ([#2794](https://github.com/vuejs/eslint-plugin-vue/pull/2794)) + +- Added new [`vue/no-negated-condition`](https://eslint.vuejs.org/rules/no-negated-condition.html) rule ([#2795](https://github.com/vuejs/eslint-plugin-vue/pull/2795)) + +### Patch Changes + +- Resolved TypeScript compatibility issues introduced by eslint-typegen ([#2790](https://github.com/vuejs/eslint-plugin-vue/pull/2790)) + +- Fixed inconsistent quotes in [`vue/block-lang`](https://eslint.vuejs.org/rules/block-lang.html) error messages ([#2805](https://github.com/vuejs/eslint-plugin-vue/pull/2805)) + +## 10.3.0 + +### Minor Changes + +- Added [`@typescript-eslint/parser`](https://typescript-eslint.io/packages/parser) as an optional peer dependency ([#2775](https://github.com/vuejs/eslint-plugin-vue/pull/2775)) + +- Add TypeScript IntelliSense support via [eslint-typegen](https://github.com/antfu/eslint-typegen) ([#2770](https://github.com/vuejs/eslint-plugin-vue/pull/2770)) + +- [`vue/no-deprecated-slot-attribute`](https://eslint.vuejs.org/rules/no-deprecated-slot-attribute.html) `ignore` option now supports regex patterns ([#2773](https://github.com/vuejs/eslint-plugin-vue/pull/2773)) + +### Patch Changes + +- Fixed false negatives when using typescript-eslint v8 in [`vue/script-indent`](https://eslint.vuejs.org/rules/script-indent.html) rule ([#2775](https://github.com/vuejs/eslint-plugin-vue/pull/2775)) + +- Update resources ([#2752](https://github.com/vuejs/eslint-plugin-vue/pull/2752)) + +- [`vue/no-restricted-html-elements`](https://eslint.vuejs.org/rules/no-restricted-html-elements.html) now also checks SVG and MathML elements ([#2755](https://github.com/vuejs/eslint-plugin-vue/pull/2755)) + +## 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 c8951f0a3..bdeb5e167 100644 --- a/README.md +++ b/README.md @@ -2,55 +2,54 @@ [![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 ## :book: Documentation -See [the official website](https://eslint.vuejs.org). +Please refer to the [official website](https://eslint.vuejs.org). ## :anchor: Versioning Policy -This plugin is following [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. -## :newspaper: Changelog +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. -This project uses [GitHub Releases](https://github.com/vuejs/eslint-plugin-vue/releases). +[Semantic Versioning]: https://semver.org/ +[ESLint's Semantic Versioning Policy]: https://github.com/eslint/eslint#semantic-versioning-policy -## :beers: Contribution Guide +## :newspaper: Releases -Contribution is welcome! +This project uses [GitHub Releases](https://github.com/vuejs/eslint-plugin-vue/releases). -See [The ESLint Vue Plugin Developer Guide](https://eslint.vuejs.org/developer-guide/). +## :beers: Contribution Guide -### Working with Rules +Contributing is welcome! See the [ESLint Vue Plugin Developer Guide](https://eslint.vuejs.org/developer-guide). -Before you start writing a new rule, please read [the official ESLint guide](https://eslint.org/docs/developer-guide/working-with-rules). +### Working With Rules -Next, in order to get an idea how does the AST of the code that you want to check looks like, use the [astexplorer.net]. -The [astexplorer.net] is a great tool to inspect ASTs, also Vue templates are supported. +Be sure to read the [official ESLint guide](https://eslint.org/docs/developer-guide/working-with-rules) before you start writing a new rule. -After opening [astexplorer.net], select `Vue` as the syntax and `vue-eslint-parser` as the parser. +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. -[astexplorer.net]: https://astexplorer.net/ +The default JavaScript parser must be replaced because [Vue.js single file components](https://vuejs.org/guide/scaling-up/sfc.html) 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/.vitepress/theme/components/eslint-code-block.vue b/docs/.vitepress/theme/components/eslint-code-block.vue new file mode 100644 index 000000000..12cd1ff8b --- /dev/null +++ b/docs/.vitepress/theme/components/eslint-code-block.vue @@ -0,0 +1,217 @@ + + + + + diff --git a/docs/.vitepress/theme/components/rules-table.vue b/docs/.vitepress/theme/components/rules-table.vue new file mode 100644 index 000000000..c517d4591 --- /dev/null +++ b/docs/.vitepress/theme/components/rules-table.vue @@ -0,0 +1,90 @@ + + + + + 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/components/eslint-code-block.vue b/docs/.vuepress/components/eslint-code-block.vue deleted file mode 100644 index 2c6abd2cc..000000000 --- a/docs/.vuepress/components/eslint-code-block.vue +++ /dev/null @@ -1,156 +0,0 @@ - - - - - diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js deleted file mode 100644 index 5bcb7913b..000000000 --- a/docs/.vuepress/config.js +++ /dev/null @@ -1,191 +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: { - apiKey: 'b2b69365da747a9a9635cda391317c36', - 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: [{ 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 220e43417..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' } @@ -113,6 +105,35 @@ tester.run('valid-next-tick', rule, { return this.$nextTick(); } }` + }, + + { + filename: 'test.vue', + code: `` + }, + + // https://github.com/vuejs/eslint-plugin-vue/issues/1776 + { + filename: 'test.vue', + code: `` } ], invalid: [ @@ -135,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/regexp.js b/tests/lib/utils/regexp.js index 830fa2a11..e27a0596f 100644 --- a/tests/lib/utils/regexp.js +++ b/tests/lib/utils/regexp.js @@ -1,6 +1,10 @@ 'use strict' -const { escape, toRegExp } = require('../../../lib/utils/regexp') +const { + escape, + toRegExp, + toRegExpGroupMatcher +} = require('../../../lib/utils/regexp') const assert = require('assert') const ESCAPED = '\\^\\$\\.\\*\\+\\?\\(\\)\\[\\]\\{\\}\\|\\\\' @@ -35,4 +39,120 @@ describe('toRegExp()', () => { assert.deepEqual(toRegExp(`${/^bar/i}`), /^bar/i) assert.deepEqual(toRegExp(`${/[\sA-Z]+/u}`), /[\sA-Z]+/u) }) + + it('should handle simple patterns', () => { + const regex = toRegExp('foo') + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), false) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), false) + }) + + it('should handle simple patterns with added flags', () => { + const regex = toRegExp('foo', { add: 'i' }) + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), false) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), true) + }) + + it('should handle regexp patterns', () => { + const regex = toRegExp('/^foo/') + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), false) + }) + + it('should handle regexp patterns with attached flags', () => { + const regex = toRegExp('/^foo/i') + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), true) + }) + + it('should handle regexp patterns with added flags', () => { + const regex = toRegExp('/^foo/', { add: 'i' }) + assert.deepEqual(regex, /^foo/i) + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), true) + }) + + it('should handle regexp patterns with removed flags', () => { + const regex = toRegExp('/^foo/i', { remove: 'i' }) + assert.deepEqual(regex, /^foo/) + assert.strictEqual(regex.test('foo'), true) + assert.strictEqual(regex.test('bar'), false) + assert.strictEqual(regex.test('foobar'), true) + assert.strictEqual(regex.test('afoo'), false) + assert.strictEqual(regex.test('afoobar'), false) + assert.strictEqual(regex.test('Foo'), false) + }) +}) + +describe('toRegExpGroupMatcher()', () => { + it('should return a function missing input', () => { + const groupMatcher = toRegExpGroupMatcher() + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), false) + assert.strictEqual(groupMatcher('bar'), false) + }) + + it('should return a function for empty array', () => { + const groupMatcher = toRegExpGroupMatcher([]) + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), false) + assert.strictEqual(groupMatcher('bar'), false) + }) + + it('should return a function for single simple pattern', () => { + const groupMatcher = toRegExpGroupMatcher(['foo']) + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('foo', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'foo'), true) + assert.strictEqual(groupMatcher('foobar'), false) + assert.strictEqual(groupMatcher('afoo', 'fooa', 'afooa', 'bar'), false) + }) + + it('should return a function for multiple simple patterns', () => { + const groupMatcher = toRegExpGroupMatcher(['foo', 'bar']) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('bar', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'foo'), true) + assert.strictEqual(groupMatcher('foobar'), false) + assert.strictEqual(groupMatcher('afoo', 'fooa', 'afooa'), false) + }) + + it('should return a function for single regexp pattern', () => { + const groupMatcher = toRegExpGroupMatcher(['/^foo/g']) + assert.strictEqual(groupMatcher(''), false) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('fooa', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'fooa'), true) + assert.strictEqual(groupMatcher('barfoo'), false) + assert.strictEqual(groupMatcher('afoo', 'afooa', 'bar'), false) + }) + + it('should return a function for multiple regexp patterns', () => { + const groupMatcher = toRegExpGroupMatcher(['/^foo/', '/bar$/gi']) + assert.strictEqual(groupMatcher('foo'), true) + assert.strictEqual(groupMatcher('Bar', 'early'), true) + assert.strictEqual(groupMatcher('late', 'matches', 'foo'), true) + assert.strictEqual(groupMatcher('barfoo'), false) + assert.strictEqual(groupMatcher('afoo', 'afooa', 'bara'), false) + }) }) 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/eslint-stylistic.js b/tests/test-utils/eslint-stylistic.js new file mode 100644 index 000000000..81fc774f3 --- /dev/null +++ b/tests/test-utils/eslint-stylistic.js @@ -0,0 +1,16 @@ +const { existsSync, readFileSync } = require('node:fs') +const path = require('node:path') + +const eslintStylisticPackagePath = path.join( + __dirname, + '../..', + 'node_modules', + '@stylistic', + 'eslint-plugin', + 'package.json' +) +const eslintStylisticVersion = existsSync(eslintStylisticPackagePath) + ? JSON.parse(readFileSync(eslintStylisticPackagePath, 'utf8')).version + : undefined + +module.exports = { eslintStylisticVersion } 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/generate-typegen.mjs b/tools/generate-typegen.mjs new file mode 100644 index 000000000..1f3b4a129 --- /dev/null +++ b/tools/generate-typegen.mjs @@ -0,0 +1,14 @@ +import fs from 'node:fs/promises' +import { pluginsToRulesDTS } from 'eslint-typegen/core' +import plugin from '../lib/index.js' + +const dts = await pluginsToRulesDTS( + { + vue: plugin + }, + { + includeAugmentation: false + } +) + +await fs.writeFile('lib/eslint-typegen.d.ts', dts) 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