├── .browserslistrc ├── .devcontainer └── devcontainer.json ├── .github └── workflows │ ├── GHPages.yml │ ├── NodeCI.yml │ └── format.yml ├── .gitignore ├── .npmrc ├── .prettierignore ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── eslint.config.mjs ├── package.json ├── prettier.config.js ├── public ├── favicon.ico └── index.html ├── renovate.json ├── shim ├── empty.js ├── eslint │ ├── index.js │ └── use-at-your-own-risk.js ├── module.js └── require-parser.js ├── src ├── App.vue ├── assets │ └── logo.png ├── components │ ├── ESLintEditor.vue │ ├── ESLintPlayground.vue │ ├── RulesSettings.vue │ └── scripts │ │ ├── rules.ts │ │ └── state │ │ ├── deserialize.ts │ │ ├── index.ts │ │ └── serialize.ts ├── main.ts └── shims-vue.d.ts ├── tsconfig.json └── vue.config.js /.browserslistrc: -------------------------------------------------------------------------------- 1 | > 1% 2 | last 2 versions 3 | not dead 4 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | // For format details, see https://aka.ms/devcontainer.json. For config options, see the 2 | // README at: https://github.com/devcontainers/templates/tree/main/src/typescript-node 3 | { 4 | "name": "Node.js & TypeScript", 5 | // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile 6 | "image": "mcr.microsoft.com/devcontainers/typescript-node:1-20-bullseye", 7 | 8 | // Features to add to the dev container. More info: https://containers.dev/features. 9 | // "features": {}, 10 | 11 | // Use 'forwardPorts' to make a list of ports inside the container available locally. 12 | // "forwardPorts": [], 13 | 14 | // Use 'postCreateCommand' to run commands after the container is created. 15 | "postCreateCommand": "npm install", 16 | 17 | // Configure tool-specific properties. 18 | "customizations": { 19 | "vscode": { 20 | "extensions": ["dbaeumer.vscode-eslint"] 21 | } 22 | } 23 | 24 | // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. 25 | // "remoteUser": "root" 26 | } 27 | -------------------------------------------------------------------------------- /.github/workflows/GHPages.yml: -------------------------------------------------------------------------------- 1 | name: GHPages 2 | 3 | on: 4 | workflow_dispatch: null 5 | push: 6 | branches: [main] 7 | schedule: 8 | - cron: "0 20 * * *" 9 | 10 | # Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | # Allow one concurrent deployment 17 | concurrency: 18 | group: pages 19 | cancel-in-progress: true 20 | 21 | jobs: 22 | # Single deploy job since we're just deploying 23 | deploy: 24 | environment: 25 | name: github-pages 26 | url: ${{ steps.deployment.outputs.page_url }} 27 | runs-on: ubuntu-latest 28 | steps: 29 | - name: Checkout 30 | uses: actions/checkout@v4 31 | - uses: actions/setup-node@v4 32 | - name: Install Packages 33 | run: npm install 34 | - name: Build docs 35 | run: npm run build 36 | - name: Setup Pages 37 | uses: actions/configure-pages@v5 38 | - name: Upload artifact 39 | uses: actions/upload-pages-artifact@v3 40 | with: 41 | path: ./dist 42 | - name: Deploy to GitHub Pages 43 | id: deployment 44 | uses: actions/deploy-pages@v4 45 | -------------------------------------------------------------------------------- /.github/workflows/NodeCI.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | pull_request: 7 | branches: [main] 8 | 9 | jobs: 10 | test-build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v4 14 | - uses: actions/setup-node@v4 15 | - name: Install Packages 16 | run: npm install 17 | - name: Lint 18 | run: npm run lint 19 | - name: Build 20 | run: npm run build 21 | -------------------------------------------------------------------------------- /.github/workflows/format.yml: -------------------------------------------------------------------------------- 1 | name: 👔 Format 2 | 3 | on: 4 | workflow_dispatch: null 5 | 6 | permissions: 7 | contents: write 8 | 9 | jobs: 10 | format: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout repo 15 | uses: actions/checkout@v4 16 | - name: Setup node 17 | uses: actions/setup-node@v4 18 | - name: Install deps 19 | run: npm install -f 20 | - name: Format 21 | run: npm run lint-fix 22 | - name: Commit 23 | run: | 24 | git config --local user.email "github-actions[bot]@users.noreply.github.com" 25 | git config --local user.name "github-actions[bot]" 26 | 27 | git add . 28 | if [ -z "$(git status --porcelain)" ]; then 29 | echo "no formatting changed" 30 | exit 0 31 | fi 32 | git commit -m "chore: format" 33 | git push 34 | echo "pushed formatting changes https://github.com/$GITHUB_REPOSITORY/commit/$(git rev-parse HEAD)" 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | /dist 4 | 5 | 6 | # local env files 7 | .env.local 8 | .env.*.local 9 | 10 | # Log files 11 | npm-debug.log* 12 | yarn-debug.log* 13 | yarn-error.log* 14 | pnpm-debug.log* 15 | 16 | # Editor directories and files 17 | .idea 18 | # .vscode 19 | *.suo 20 | *.ntvs* 21 | *.njsproj 22 | *.sln 23 | *.sw? 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | force=true 3 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /dist 3 | !/.vscode 4 | !/.github -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | "javascriptreact", 5 | "vue", 6 | "typescript", 7 | "json", 8 | "jsonc", 9 | "yaml" 10 | ], 11 | "typescript.validate.enable": true, 12 | "javascript.validate.enable": false, 13 | "typescript.tsdk": "node_modules/typescript/lib", 14 | "editor.codeActionsOnSave": { 15 | "source.fixAll.eslint": "explicit" 16 | }, 17 | "vetur.validation.template": false, 18 | "editor.formatOnSave": true, 19 | "editor.defaultFormatter": "esbenp.prettier-vscode" 20 | } 21 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Yosuke Ota 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # [eslint-plugin-vue] Online Playground 2 | 3 | https://ota-meshi.github.io/eslint-plugin-vue-demo/ 4 | 5 | This project is heavily inspired by [vue-eslint-demo](https://github.com/mysticatea/vue-eslint-demo). 6 | 7 | [eslint-plugin-vue]: https://eslint.vuejs.org/ 8 | -------------------------------------------------------------------------------- /eslint.config.mjs: -------------------------------------------------------------------------------- 1 | /* eslint no-unused-vars: 2 -- js */ 2 | import globals from "globals" 3 | import * as tsParser from "typescript-eslint-parser-for-extra-files" 4 | import parser from "vue-eslint-parser" 5 | import myPlugin from "@ota-meshi/eslint-plugin" 6 | import tseslint from "typescript-eslint" 7 | 8 | export default [ 9 | { 10 | ignores: ["node_modules", "dist", "!.vscode", "!.github", "!.devcontainer"], 11 | }, 12 | ...myPlugin.config({ 13 | vue3: true, 14 | node: true, 15 | ts: true, 16 | json: true, 17 | yaml: true, 18 | prettier: true, 19 | }), 20 | { 21 | languageOptions: { 22 | globals: { 23 | ...globals.node, 24 | process: "readonly", 25 | }, 26 | sourceType: "module", 27 | }, 28 | 29 | rules: { 30 | "no-console": "off", 31 | "no-debugger": "off", 32 | "no-process-env": "off", 33 | "@typescript-eslint/no-explicit-any": "off", 34 | "no-unused-vars": "off", 35 | 36 | "prettier/prettier": [ 37 | "error", 38 | {}, 39 | { 40 | usePrettierrc: true, 41 | }, 42 | ], 43 | "default-case": "off", 44 | }, 45 | }, 46 | { 47 | files: ["**/*.ts"], 48 | 49 | languageOptions: { 50 | parser: tsParser, 51 | sourceType: "module", 52 | 53 | parserOptions: { 54 | project: "./tsconfig.json", 55 | }, 56 | }, 57 | }, 58 | ...tseslint.config({ 59 | files: ["**/*.vue"], 60 | extends: [tseslint.configs.disableTypeChecked], 61 | }), 62 | { 63 | files: ["**/*.vue"], 64 | 65 | languageOptions: { 66 | parser, 67 | sourceType: "module", 68 | 69 | parserOptions: { 70 | parser: { 71 | ts: "typescript-eslint-parser-for-extra-files", 72 | js: "typescript-eslint-parser-for-extra-files", 73 | }, 74 | 75 | project: "./tsconfig.json", 76 | extraFileExtensions: [".vue"], 77 | }, 78 | }, 79 | 80 | rules: { 81 | "@typescript-eslint/no-explicit-any": "off", 82 | "@typescript-eslint/array-type": "error", 83 | 84 | "@typescript-eslint/explicit-module-boundary-types": [ 85 | "error", 86 | { 87 | allowArgumentsExplicitlyTypedAsAny: true, 88 | }, 89 | ], 90 | 91 | "@typescript-eslint/consistent-type-imports": "error", 92 | "@typescript-eslint/adjacent-overload-signatures": "error", 93 | "@typescript-eslint/await-thenable": "error", 94 | "@typescript-eslint/ban-ts-comment": "error", 95 | 96 | "@typescript-eslint/naming-convention": [ 97 | "error", 98 | { 99 | selector: "default", 100 | format: ["camelCase"], 101 | leadingUnderscore: "allow", 102 | trailingUnderscore: "allow", 103 | }, 104 | { 105 | selector: "variable", 106 | format: ["camelCase", "UPPER_CASE"], 107 | leadingUnderscore: "allow", 108 | trailingUnderscore: "allow", 109 | }, 110 | { 111 | selector: "typeLike", 112 | format: ["PascalCase"], 113 | }, 114 | { 115 | selector: "memberLike", 116 | format: ["camelCase", "UPPER_CASE"], 117 | leadingUnderscore: "allow", 118 | trailingUnderscore: "allow", 119 | }, 120 | { 121 | selector: "import", 122 | format: ["camelCase", "PascalCase", "UPPER_CASE"], 123 | }, 124 | { 125 | selector: "property", 126 | format: null, 127 | }, 128 | { 129 | selector: "method", 130 | format: null, 131 | }, 132 | ], 133 | 134 | "@typescript-eslint/consistent-type-assertions": "error", 135 | "@typescript-eslint/explicit-member-accessibility": "error", 136 | "@typescript-eslint/no-array-constructor": "error", 137 | "@typescript-eslint/no-empty-interface": "error", 138 | "@typescript-eslint/no-extraneous-class": "error", 139 | "@typescript-eslint/no-floating-promises": "error", 140 | "@typescript-eslint/no-for-in-array": "error", 141 | "@typescript-eslint/no-inferrable-types": "error", 142 | "@typescript-eslint/no-misused-new": "error", 143 | "@typescript-eslint/no-misused-promises": "error", 144 | "@typescript-eslint/parameter-properties": "error", 145 | "@typescript-eslint/no-require-imports": "off", 146 | 147 | "@typescript-eslint/no-this-alias": [ 148 | "error", 149 | { 150 | allowDestructuring: true, 151 | }, 152 | ], 153 | 154 | "@typescript-eslint/no-unnecessary-qualifier": "error", 155 | "@typescript-eslint/no-unnecessary-type-arguments": "error", 156 | "@typescript-eslint/no-unnecessary-type-assertion": "error", 157 | "@typescript-eslint/no-var-requires": "error", 158 | "@typescript-eslint/prefer-function-type": "error", 159 | "@typescript-eslint/prefer-includes": "error", 160 | "@typescript-eslint/prefer-namespace-keyword": "error", 161 | "@typescript-eslint/prefer-readonly": "error", 162 | "@typescript-eslint/prefer-regexp-exec": "error", 163 | "@typescript-eslint/prefer-string-starts-ends-with": "error", 164 | "@typescript-eslint/restrict-plus-operands": "error", 165 | "@typescript-eslint/require-array-sort-compare": "error", 166 | "@typescript-eslint/triple-slash-reference": "error", 167 | 168 | "@typescript-eslint/unbound-method": [ 169 | "off", 170 | { 171 | ignoreStatic: true, 172 | }, 173 | ], 174 | 175 | "@typescript-eslint/unified-signatures": "error", 176 | camelcase: "off", 177 | "no-empty-function": "off", 178 | "@typescript-eslint/no-empty-function": "error", 179 | "no-useless-constructor": "off", 180 | "@typescript-eslint/no-useless-constructor": "error", 181 | "require-await": "off", 182 | "@typescript-eslint/require-await": "error", 183 | "no-use-before-define": "off", 184 | 185 | "@typescript-eslint/no-use-before-define": [ 186 | "error", 187 | { 188 | functions: false, 189 | classes: true, 190 | variables: true, 191 | ignoreTypeReferences: true, 192 | }, 193 | ], 194 | 195 | "no-unused-vars": "off", 196 | 197 | "@typescript-eslint/no-unused-vars": [ 198 | "error", 199 | { 200 | argsIgnorePattern: "^_", 201 | }, 202 | ], 203 | 204 | "@typescript-eslint/no-unsafe-assignment": "off", 205 | 206 | "@typescript-eslint/no-unsafe-member-access": "error", 207 | "@typescript-eslint/no-unsafe-argument": "error", 208 | }, 209 | }, 210 | ] 211 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "eslint-plugin-vue-demo", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "serve": "vue-cli-service serve", 7 | "build": "vue-cli-service build", 8 | "lint": "eslint .", 9 | "lint-fix": "eslint . --fix", 10 | "format": "prettier --write ." 11 | }, 12 | "dependencies": { 13 | "@ota-meshi/site-kit-eslint-editor-vue": "^0.2.0", 14 | "@stylistic/eslint-plugin": "~4.4.0", 15 | "@typescript-eslint/parser": "~8.33.0", 16 | "assert": "^2.0.0", 17 | "eslint": "~9.28.0", 18 | "eslint-plugin-vue": "~10.1.0", 19 | "eslint-plugin-vuejs-accessibility": "~2.4.0", 20 | "globals": "^16.0.0", 21 | "pako": "^2.0.3", 22 | "path-browserify": "^1.0.1", 23 | "tslib": "^2.3.0", 24 | "typescript": "~5.8.0", 25 | "vue": "^3.4.23", 26 | "vue-eslint-parser": "~10.1.0" 27 | }, 28 | "devDependencies": { 29 | "@eslint/eslintrc": "^3.1.0", 30 | "@eslint/js": "^9.6.0", 31 | "@ota-meshi/eslint-plugin": "^0.17.5", 32 | "@types/eslint": "^9.0.0", 33 | "@types/pako": "^2.0.0", 34 | "@vue/cli-plugin-typescript": "^5.0.8", 35 | "@vue/cli-service": "^5.0.8", 36 | "@vue/eslint-config-prettier": "^10.0.0", 37 | "@vue/eslint-config-typescript": "^14.0.0", 38 | "eslint-plugin-eslint-comments": "^3.2.0", 39 | "eslint-plugin-jsdoc": "^50.0.0", 40 | "eslint-plugin-json-schema-validator": "^5.0.0", 41 | "eslint-plugin-jsonc": "^2.0.0", 42 | "eslint-plugin-n": "^17.0.0", 43 | "eslint-plugin-prettier": "^5.0.0", 44 | "eslint-plugin-regexp": "^2.0.0", 45 | "eslint-plugin-yml": "^1.0.0", 46 | "monaco-editor": "^0.52.0", 47 | "prettier": "^3.0.0", 48 | "string-replace-loader": "^3.0.3", 49 | "typescript-eslint": "^8.0.0", 50 | "typescript-eslint-parser-for-extra-files": "^0.9.0" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | semi: false, 4 | trailingComma: "all", 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ota-meshi/eslint-plugin-vue-demo/589a3bca2a9dbe86a970b08645ab84bdb5b810e2/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | <%= htmlWebpackPlugin.options.title %> 9 | 10 | 11 | 18 |
19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": [ 3 | "config:base", 4 | ":preserveSemverRanges", 5 | ":disableDependencyDashboard" 6 | ], 7 | "packageRules": [ 8 | { 9 | "updateTypes": ["major", "minor", "patch", "pin", "digest"], 10 | "automerge": true 11 | }, 12 | { 13 | "depTypeList": ["devDependencies"], 14 | "automerge": true 15 | } 16 | ] 17 | } 18 | -------------------------------------------------------------------------------- /shim/empty.js: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ota-meshi/eslint-plugin-vue-demo/589a3bca2a9dbe86a970b08645ab84bdb5b810e2/shim/empty.js -------------------------------------------------------------------------------- /shim/eslint/index.js: -------------------------------------------------------------------------------- 1 | /* eslint n/no-unsupported-features/es-syntax:0 -- ignore */ 2 | import { Linter } from "../../node_modules/eslint/lib/linter/index.js" 3 | export { Linter } 4 | export default { Linter } 5 | -------------------------------------------------------------------------------- /shim/eslint/use-at-your-own-risk.js: -------------------------------------------------------------------------------- 1 | /* eslint n/no-unsupported-features/es-syntax:0 -- ignore */ 2 | import rules from "../../node_modules/eslint/lib/rules/index.js" 3 | export const builtinRules = rules 4 | export default { builtinRules } 5 | -------------------------------------------------------------------------------- /shim/module.js: -------------------------------------------------------------------------------- 1 | const requireParser = require("./require-parser") 2 | module.exports = { 3 | createRequire: () => requireParser, 4 | } 5 | -------------------------------------------------------------------------------- /shim/require-parser.js: -------------------------------------------------------------------------------- 1 | /* globals loadedParsers -- shim */ 2 | module.exports = function (nm) { 3 | if (nm === "espree") { 4 | // eslint-disable-next-line n/no-extraneous-require -- ignore 5 | return require("espree") 6 | } 7 | if (typeof loadedParsers !== "undefined" && loadedParsers.parsers[nm]) { 8 | return loadedParsers.parsers[nm] 9 | } 10 | 11 | throw new Error(`Parser "${nm}" not loaded`) 12 | } 13 | -------------------------------------------------------------------------------- /src/App.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 65 | 66 | 125 | -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/ota-meshi/eslint-plugin-vue-demo/589a3bca2a9dbe86a970b08645ab84bdb5b810e2/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/ESLintEditor.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 160 | 161 | 179 | 180 | 181 | -------------------------------------------------------------------------------- /src/components/ESLintPlayground.vue: -------------------------------------------------------------------------------- 1 | 39 | 40 | 199 | 258 | -------------------------------------------------------------------------------- /src/components/RulesSettings.vue: -------------------------------------------------------------------------------- 1 | 148 | 149 | 312 | 313 | 429 | -------------------------------------------------------------------------------- /src/components/scripts/rules.ts: -------------------------------------------------------------------------------- 1 | import { builtinRules } from "eslint/use-at-your-own-risk" 2 | import { rules as vueRules } from "eslint-plugin-vue" 3 | import { rules as a11yRules } from "eslint-plugin-vuejs-accessibility" 4 | 5 | const coreRules = new Set(builtinRules.values()) 6 | 7 | export type Rule = { 8 | ruleId: string 9 | rule: any 10 | url: string 11 | classes: string 12 | } 13 | export type Category = { 14 | title: string 15 | rules: Rule[] 16 | isTarget?: (ruleCategories: string[] | void, rule: any) => boolean | void 17 | classes: string 18 | } 19 | 20 | export const categories: Category[] = [ 21 | { 22 | title: "Base Rules", 23 | isTarget: (ruleCategories) => 24 | ruleCategories && ruleCategories.includes("base"), 25 | rules: [], 26 | classes: "eslint-plugin-vue-category", 27 | }, 28 | { 29 | title: "Priority A: Essential", 30 | isTarget: (ruleCategories) => 31 | ruleCategories && 32 | ruleCategories.includes("vue3-essential") && 33 | ruleCategories.includes("essential"), 34 | rules: [], 35 | classes: "eslint-plugin-vue-category", 36 | }, 37 | { 38 | title: "Priority A: Essential (for Vue.js 3.x)", 39 | isTarget: (ruleCategories) => 40 | ruleCategories && 41 | ruleCategories.includes("vue3-essential") && 42 | !ruleCategories.includes("essential"), 43 | rules: [], 44 | classes: "eslint-plugin-vue-category", 45 | }, 46 | { 47 | title: "Priority A: Essential (for Vue.js 2.x)", 48 | isTarget: (ruleCategories) => 49 | ruleCategories && 50 | !ruleCategories.includes("vue3-essential") && 51 | ruleCategories.includes("essential"), 52 | rules: [], 53 | classes: "eslint-plugin-vue-category", 54 | }, 55 | { 56 | title: "Priority B: Strongly Recommended", 57 | isTarget: (ruleCategories) => 58 | ruleCategories && 59 | ruleCategories.includes("vue3-strongly-recommended") && 60 | ruleCategories.includes("strongly-recommended"), 61 | rules: [], 62 | classes: "eslint-plugin-vue-category", 63 | }, 64 | { 65 | title: "Priority B: Strongly Recommended (for Vue.js 3.x)", 66 | isTarget: (ruleCategories) => 67 | ruleCategories && 68 | ruleCategories.includes("vue3-strongly-recommended") && 69 | !ruleCategories.includes("strongly-recommended"), 70 | rules: [], 71 | classes: "eslint-plugin-vue-category", 72 | }, 73 | { 74 | title: "Priority B: Strongly Recommended (for Vue.js 2.x)", 75 | isTarget: (ruleCategories) => 76 | ruleCategories && 77 | !ruleCategories.includes("vue3-strongly-recommended") && 78 | ruleCategories.includes("strongly-recommended"), 79 | rules: [], 80 | classes: "eslint-plugin-vue-category", 81 | }, 82 | { 83 | title: "Priority C: Recommended", 84 | isTarget: (ruleCategories) => 85 | ruleCategories && 86 | ruleCategories.includes("vue3-recommended") && 87 | ruleCategories.includes("recommended"), 88 | rules: [], 89 | classes: "eslint-plugin-vue-category", 90 | }, 91 | { 92 | title: "Priority C: Recommended (for Vue.js 3.x)", 93 | isTarget: (ruleCategories) => 94 | ruleCategories && 95 | ruleCategories.includes("vue3-recommended") && 96 | !ruleCategories.includes("recommended"), 97 | rules: [], 98 | classes: "eslint-plugin-vue-category", 99 | }, 100 | { 101 | title: "Priority C: Recommended (for Vue.js 2.x)", 102 | isTarget: (ruleCategories) => 103 | ruleCategories && 104 | !ruleCategories.includes("vue3-recommended") && 105 | ruleCategories.includes("recommended"), 106 | rules: [], 107 | classes: "eslint-plugin-vue-category", 108 | }, 109 | { 110 | title: "Uncategorized", 111 | isTarget: (_ruleCategories, rule) => 112 | Object.values(vueRules).includes(rule) && !rule.meta.docs.extensionRule, 113 | rules: [], 114 | classes: "eslint-plugin-vue-category", 115 | }, 116 | { 117 | title: "Extension Rules", 118 | isTarget: (_ruleCategories, rule) => 119 | Object.values(vueRules).includes(rule) && rule.meta.docs.extensionRule, 120 | rules: [], 121 | classes: "eslint-plugin-vue-category", 122 | }, 123 | { 124 | title: "Possible Errors", 125 | isTarget: (_ruleCategories, rule) => 126 | coreRules.has(rule) && rule.meta.type === "problem", 127 | rules: [], 128 | classes: "eslint-category", 129 | }, 130 | { 131 | title: "Suggestions", 132 | isTarget: (_ruleCategories, rule) => 133 | coreRules.has(rule) && rule.meta.type === "suggestion", 134 | rules: [], 135 | classes: "eslint-category", 136 | }, 137 | { 138 | title: "Layout & Formatting", 139 | isTarget: (_ruleCategories, rule) => 140 | coreRules.has(rule) && rule.meta.type === "layout", 141 | rules: [], 142 | classes: "eslint-category", 143 | }, 144 | { 145 | title: "eslint-plugin-vuejs-accessibility", 146 | isTarget: (_ruleCategories, rule) => 147 | Object.values(a11yRules).includes(rule), 148 | rules: [], 149 | classes: "eslint-plugin-vuejs-accessibility-category", 150 | }, 151 | ] 152 | export const DEFAULT_RULES_CONFIG: Record = {} 153 | 154 | for (const [baseRuleId, unknownRule] of Object.entries(vueRules)) { 155 | const rule = unknownRule 156 | if (rule.meta.deprecated) { 157 | continue 158 | } 159 | const ruleId = `vue/${baseRuleId}` 160 | const data: Rule = { 161 | ruleId, 162 | rule, 163 | url: rule.meta.docs.url || "", 164 | classes: "eslint-plugin-vue-rule", 165 | } 166 | const ruleCategories = rule.meta.docs.category 167 | ? [rule.meta.docs.category] 168 | : rule.meta.docs.categories 169 | categories.find((c) => c.isTarget?.(ruleCategories, rule))?.rules.push(data) 170 | 171 | if ( 172 | ruleCategories?.includes("base") || 173 | ruleCategories?.includes("vue3-essential") 174 | ) { 175 | DEFAULT_RULES_CONFIG[ruleId] = "error" 176 | } 177 | } 178 | for (const [baseRuleId, rule] of Object.entries(a11yRules)) { 179 | if (rule.meta!.deprecated) { 180 | continue 181 | } 182 | const ruleId = `vuejs-accessibility/${baseRuleId}` 183 | const data: Rule = { 184 | ruleId, 185 | rule, 186 | url: rule.meta!.docs!.url || "", 187 | classes: "eslint-plugin-vuejs-accessibility-rule", 188 | } 189 | categories 190 | .find( 191 | (c) => 192 | c.isTarget?.([], rule) && 193 | c.classes?.includes("eslint-plugin-vuejs-accessibility"), 194 | ) 195 | ?.rules.push(data) 196 | } 197 | for (const [ruleId, rule] of builtinRules) { 198 | if (rule.meta?.deprecated) { 199 | continue 200 | } 201 | const data: Rule = { 202 | ruleId, 203 | rule, 204 | url: rule.meta?.docs?.url || "", 205 | classes: "eslint-rule", 206 | } 207 | categories.find((c) => c.isTarget?.([], rule))?.rules.push(data) 208 | 209 | if (rule.meta?.docs?.recommended) { 210 | DEFAULT_RULES_CONFIG[ruleId] = "error" 211 | } 212 | } 213 | /** get rule */ 214 | export function getRule(ruleId: string | null): Rule | null { 215 | if (!ruleId) { 216 | return null 217 | } 218 | for (const category of categories) { 219 | for (const rule of category.rules) { 220 | if (rule.ruleId === ruleId) { 221 | return rule 222 | } 223 | } 224 | } 225 | let rule: any, classes: string 226 | if (ruleId.startsWith("vue/")) { 227 | rule = vueRules[ruleId.slice(4)] 228 | classes = "eslint-plugin-vue-rule" 229 | } else if (ruleId.startsWith("vuejs-accessibility/")) { 230 | rule = a11yRules[ruleId.slice(20) as keyof typeof a11yRules] 231 | classes = "eslint-plugin-vuejs-accessibility-rule" 232 | } else { 233 | rule = builtinRules.get(ruleId) 234 | classes = "eslint-rule" 235 | } 236 | return rule 237 | ? { 238 | ruleId, 239 | rule, 240 | url: rule.meta?.docs?.url || "", 241 | classes, 242 | } 243 | : null 244 | } 245 | -------------------------------------------------------------------------------- /src/components/scripts/state/deserialize.ts: -------------------------------------------------------------------------------- 1 | import pako from "pako" 2 | 3 | /** 4 | * Deserialize a given serialized string then update this object. 5 | * @param {string} serializedString A serialized string. 6 | * @returns {object} The deserialized state. 7 | */ 8 | export function deserializeState(serializedString: string): { 9 | code?: string 10 | rules?: Record 11 | parser?: string | Record 12 | } { 13 | const state: { 14 | code?: string 15 | rules?: Record 16 | parser?: string | Record 17 | } = { 18 | code: undefined, 19 | rules: undefined, 20 | parser: undefined, 21 | } 22 | 23 | if (serializedString === "") { 24 | return state 25 | } 26 | 27 | try { 28 | const compressedString = window.atob(serializedString) 29 | const uint8Arr = pako.inflate( 30 | Uint8Array.from(compressedString, (c) => c.charCodeAt(0)), 31 | ) 32 | const jsonText = new TextDecoder().decode(uint8Arr) 33 | const json = JSON.parse(jsonText) 34 | 35 | if (typeof json === "object" && json != null) { 36 | if (typeof json.code === "string") { 37 | state.code = json.code 38 | } 39 | if (typeof json.parser === "string" || typeof json.parser === "object") { 40 | state.parser = json.parser 41 | } 42 | if (typeof json.rules === "object" && json.rules != null) { 43 | state.rules = {} 44 | for (const id of Object.keys(json.rules)) { 45 | state.rules[id] = json.rules[id] === 2 ? "error" : "off" 46 | } 47 | } 48 | } 49 | } catch (error) { 50 | console.error(error) 51 | } 52 | 53 | return state 54 | } 55 | -------------------------------------------------------------------------------- /src/components/scripts/state/index.ts: -------------------------------------------------------------------------------- 1 | export * from "./deserialize" 2 | export * from "./serialize" 3 | -------------------------------------------------------------------------------- /src/components/scripts/state/serialize.ts: -------------------------------------------------------------------------------- 1 | import pako from "pako" 2 | 3 | /** 4 | * Get only enabled rules to make the serialized data smaller. 5 | * @param {object} allRules The rule settings. 6 | * @returns {object} The rule settings for the enabled rules. 7 | */ 8 | function getEnabledRules( 9 | allRules: Record, 10 | ): Record { 11 | return Object.keys(allRules).reduce( 12 | (map, id) => { 13 | if (allRules[id] === "error") { 14 | map[id] = 2 15 | } 16 | return map 17 | }, 18 | {} as Record, 19 | ) 20 | } 21 | 22 | /** 23 | * Serialize a given state as a base64 string. 24 | * @param {State} state The state to serialize. 25 | * @returns {string} The serialized string. 26 | */ 27 | export function serializeState(state: { 28 | code?: string 29 | rules?: Record 30 | parser?: string | Record 31 | }): string { 32 | const saveData = { 33 | code: state.code, 34 | rules: state.rules ? getEnabledRules(state.rules) : undefined, 35 | parser: state.parser, 36 | } 37 | const jsonString = JSON.stringify(saveData) 38 | const uint8Arr = new TextEncoder().encode(jsonString) 39 | const compressedString = String.fromCharCode(...pako.deflate(uint8Arr)) 40 | const base64 = 41 | (typeof window !== "undefined" && window.btoa(compressedString)) || 42 | compressedString 43 | 44 | console.log( 45 | `The compress rate of serialized string: ${( 46 | (100 * base64.length) / 47 | jsonString.length 48 | ).toFixed(1)}% (${jsonString.length}B → ${base64.length}B)`, 49 | ) 50 | 51 | return base64 52 | } 53 | -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from "vue" 2 | import App from "./App.vue" 3 | 4 | if (typeof window !== "undefined" && typeof window.process === "undefined") { 5 | window.process = { 6 | env: {}, 7 | cwd: () => "", 8 | } as any 9 | } 10 | createApp(App).mount("#app") 11 | -------------------------------------------------------------------------------- /src/shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module "*.vue" { 2 | import type { DefineComponent } from "vue" 3 | const component: DefineComponent 4 | export default component 5 | } 6 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "module": "esnext", 5 | "strict": true, 6 | "jsx": "preserve", 7 | "importHelpers": true, 8 | "moduleResolution": "node", 9 | "skipLibCheck": true, 10 | "esModuleInterop": true, 11 | "allowSyntheticDefaultImports": true, 12 | "downlevelIteration": true, 13 | "sourceMap": true, 14 | "baseUrl": ".", 15 | "types": ["webpack-env"], 16 | "paths": { 17 | "@/*": ["src/*"], 18 | "*": ["typings/*"] 19 | }, 20 | "lib": ["esnext", "dom", "dom.iterable", "scripthost"] 21 | }, 22 | "include": [ 23 | "src/**/*.ts", 24 | "src/**/*.tsx", 25 | "src/**/*.vue", 26 | "tests/**/*.ts", 27 | "tests/**/*.tsx" 28 | ], 29 | "exclude": ["node_modules"] 30 | } 31 | -------------------------------------------------------------------------------- /vue.config.js: -------------------------------------------------------------------------------- 1 | // eslint-disable-next-line n/no-extraneous-require -- OK 2 | const webpack = require("webpack") 3 | const path = require("path") 4 | process.env.VUE_APP_BUILD_AT = new Date().toLocaleString(undefined, { 5 | timeZoneName: "short", 6 | }) 7 | 8 | // check for versions 9 | console.log( 10 | [ 11 | require("eslint-plugin-vue/package.json"), 12 | require("vue-eslint-parser/package.json"), 13 | require("eslint/package.json"), 14 | require("@typescript-eslint/parser/package.json"), 15 | require("typescript/package.json"), 16 | require("eslint-plugin-vuejs-accessibility/package.json"), 17 | ] 18 | .map((pkg) => `${pkg.name}@${pkg.version}`) 19 | .join("\n"), 20 | ) 21 | 22 | module.exports = { 23 | publicPath: "/eslint-plugin-vue-demo/", 24 | configureWebpack(_config, _isServer) { 25 | return { 26 | resolve: { 27 | alias: { 28 | module: path.resolve("./shim/module.js"), 29 | globby: path.resolve("./shim/empty"), 30 | "fast-glob": path.resolve("./shim/empty"), 31 | eslint$: path.resolve("./shim/eslint/index.js"), 32 | "eslint/use-at-your-own-risk": path.resolve( 33 | "./shim/eslint//use-at-your-own-risk.js", 34 | ), 35 | esquery: path.resolve("./node_modules/esquery/dist/esquery.min.js"), 36 | "@eslint/eslintrc/universal": path.resolve( 37 | "./node_modules/@eslint/eslintrc/dist/eslintrc-universal.cjs", 38 | ), 39 | }, 40 | fallback: { 41 | assert: require.resolve("assert/"), 42 | path: require.resolve("path-browserify"), 43 | fs: false, 44 | }, 45 | }, 46 | externals: { "node:os": "{}", "node:fs": "{}", "node:util": "{}" }, 47 | plugins: [ 48 | new webpack.NormalModuleReplacementPlugin(/node:/, (resource) => { 49 | const mod = resource.request.replace(/^node:/, "") 50 | if (mod === "assert") { 51 | resource.request = "assert" 52 | } else if (mod === "path") { 53 | resource.request = "path-browserify" 54 | } else { 55 | // throw new Error(`Not found ${mod}`) 56 | } 57 | }), 58 | ], 59 | module: { 60 | rules: [ 61 | { 62 | // Patch for `vue-eslint-parser` 63 | test: /node_modules\/vue-eslint-parser\/index\.js$/u, 64 | loader: "string-replace-loader", 65 | options: { 66 | search: "require\\(parser\\)", 67 | replace: (original) => 68 | `require(${JSON.stringify( 69 | require.resolve("./shim/require-parser.js"), 70 | )})(parser) // ${original}`, 71 | flags: "", 72 | }, 73 | }, 74 | ], 75 | }, 76 | } 77 | }, 78 | } 79 | --------------------------------------------------------------------------------