├── .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 |
2 |
3 |
33 |
34 |
35 |
36 |
37 |
38 |
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 |
162 |
178 |
179 |
180 |
181 |
--------------------------------------------------------------------------------
/src/components/ESLintPlayground.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
11 |
18 |
19 |
20 | -
26 | [{{ msg.line }}:{{ msg.column }}]: {{ msg.message }} (
30 | {{ msg.ruleId }} )
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
199 |
258 |
--------------------------------------------------------------------------------
/src/components/RulesSettings.vue:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
12 |
13 |
62 |
146 |
147 |
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 |
--------------------------------------------------------------------------------