├── .eslintignore ├── .eslintrc.yml ├── .gitattributes ├── .gitignore ├── .npmrc ├── .travis.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── eslint-rules ├── .eslintrc.yml └── require-noopener.js ├── package.json ├── scripts ├── deploy.js └── make-versions.js ├── src ├── actions │ ├── edit-code.js │ ├── index.js │ ├── select-indent-size.js │ ├── select-indent-type.js │ ├── select-parser.js │ └── select-rule-severity.js ├── app-footer.vue ├── app-header.vue ├── app.vue ├── appcache-toast.vue ├── configuration-category-header.vue ├── configuration-category-item.vue ├── configuration-category.vue ├── configuration-parser-select.vue ├── configuration-rules-select.vue ├── configuration.vue ├── index.html ├── index.js ├── lib │ ├── clone.js │ ├── eslint.js │ ├── get-rule-url.js │ └── rule-categories.js ├── md-icon.vue ├── message-list.vue ├── playground.vue ├── state │ ├── default-code.js │ ├── default-config.js │ ├── deserialize.js │ ├── index.js │ ├── initial-state.js │ └── serialize.js └── versions.js └── webpack.config.js /.eslintignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /src/core-rules.js 3 | /src/vue-rules.js 4 | /test.* 5 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - plugin:@mysticatea/es2018 4 | - plugin:@mysticatea/+modules 5 | - plugin:@mysticatea/+browser 6 | 7 | rules: 8 | no-trailing-spaces: error 9 | require-unicode-regexp: "off" 10 | 11 | overrides: 12 | - files: "*.vue" 13 | rules: 14 | "@mysticatea/vue/component-name-in-template-casing": [error, kebab-case] 15 | require-noopener: error 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /dist 2 | /node_modules 3 | /npm-debug.log 4 | /test.* 5 | .DS_Store 6 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: false 2 | 3 | branches: 4 | only: 5 | - master 6 | 7 | language: node_js 8 | node_js: "10" 9 | 10 | script: 11 | - git config --local user.name "Travis CI" && npm run deploy 12 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "eslint.validate": [ 3 | "javascript", 4 | {"autoFix": true, "language": "vue"} 5 | ], 6 | "eslint.options": { 7 | "rulePaths": ["eslint-rules"] 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Toru Nagashima 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 | # vue-eslint-demo 2 | 3 | The online demo to check [eslint-plugin-vue](https://github.com/vuejs/eslint-plugin-vue#readme). 4 | 5 | ## ⤴️ Motivation 6 | 7 | - Let's make the reproduce code/config of bugs. It would help issue handling. 8 | 9 | ## 💿 Installation 10 | 11 | Open https://mysticatea.github.io/vue-eslint-demo/ 12 | 13 | - It does not support browsers which don't support ES2015. 14 | 15 | ## 📖 Usage 16 | 17 | - Choose rules in sidebar to enable. 18 | - Input code the center area. 19 | - Right area shows the result of `eslint --fix`. 20 | - Bottom area shows the error list. 21 | 22 | ## 📰 Changelog 23 | 24 | - [GitHub Releases](https://github.com/mysticatea/vue-eslint-demo/releases) 25 | 26 | ## 🍻 Contributing 27 | 28 | Welcome contributing! 29 | 30 | Please use GitHub's Issues/PRs. 31 | 32 | ### Development Tools 33 | 34 | - `npm run build` builds the app into `/dist` directory. 35 | - `npm run clean` removes `/dist` directory. 36 | - `npm run deploy` builds the app, then updates `gh-pages` branch, then pushes it to GitHub. 37 | - `npm run update-deps` installs the latest `eslint`, `eslint-plugin-vue`, and `vue-eslint-parser`. 38 | - `npm run watch` runs the app with `webpack-dev-server`. 39 | -------------------------------------------------------------------------------- /eslint-rules/.eslintrc.yml: -------------------------------------------------------------------------------- 1 | root: true 2 | extends: 3 | - plugin:@mysticatea/es2018 4 | - plugin:@mysticatea/+eslint-plugin 5 | -------------------------------------------------------------------------------- /eslint-rules/require-noopener.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | module.exports = context => 4 | context.parserServices.defineTemplateBodyVisitor({ 5 | "VElement[name='a']"(node) { 6 | const attributes = node.startTag.attributes 7 | const hasTargetBlank = attributes.some( 8 | attribute => 9 | !attribute.directive && 10 | attribute.key.name === "target" && 11 | attribute.value != null && 12 | attribute.value.value === "_blank", 13 | ) 14 | const hasRelNoopener = attributes.some( 15 | attribute => 16 | !attribute.directive && 17 | attribute.key.name === "rel" && 18 | attribute.value != null && 19 | attribute.value.value === "noopener", 20 | ) 21 | 22 | if (hasTargetBlank && !hasRelNoopener) { 23 | context.report({ 24 | node: node.startTag, 25 | message: "Use 'rel=\"noopener\"' to open new tab.", 26 | *fix(fixer) { 27 | const lastAttribute = attributes[attributes.length - 1] 28 | yield fixer.insertTextAfter( 29 | lastAttribute, 30 | ' rel="noopener"', 31 | ) 32 | }, 33 | }) 34 | } 35 | }, 36 | }) 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-eslint-demo", 3 | "version": "1.8.0", 4 | "description": "The online demo to check `eslint-plugin-vue`.", 5 | "engines": { 6 | "node": ">=8" 7 | }, 8 | "files": [ 9 | "dist" 10 | ], 11 | "dependencies": {}, 12 | "devDependencies": { 13 | "@babel/core": "^7.1.2", 14 | "@babel/plugin-syntax-dynamic-import": "^7.0.0", 15 | "@babel/plugin-transform-runtime": "^7.1.0", 16 | "@babel/polyfill": "^7.0.0", 17 | "@babel/preset-env": "^7.1.0", 18 | "@babel/runtime": "^7.1.2", 19 | "@mysticatea/eslint-plugin": "^6.0.0", 20 | "appcache-manifest": "^2.1.0", 21 | "babel-eslint": "^10.0.1", 22 | "babel-loader": "^8.0.4", 23 | "cpx": "^1.5.0", 24 | "cross-env": "^5.1.1", 25 | "css-loader": "^1.0.0", 26 | "eslint": "^5.7.0", 27 | "eslint-plugin-vue": "^5.0.0-beta.3", 28 | "eslint4b": "^5.7.0", 29 | "file-loader": "^2.0.0", 30 | "fs-extra": "^7.0.0", 31 | "material-design-icons": "^3.0.1", 32 | "npm-run-all": "^4.1.2", 33 | "pako": "^1.0.6", 34 | "postcss-loader": "^3.0.0", 35 | "postcss-preset-env": "^6.1.2", 36 | "rimraf": "^2.6.2", 37 | "string-replace-loader": "^1.3.0", 38 | "typescript": "~3.1.6", 39 | "typescript-eslint-parser": "^21.0.1", 40 | "url-loader": "^1.1.2", 41 | "vue": "^2.5.9", 42 | "vue-eslint-editor": "^0.1.2", 43 | "vue-eslint-parser": "^3.2.2", 44 | "vue-loader": "^15.4.2", 45 | "vue-template-compiler": "^2.5.9", 46 | "webpack": "^4.21.0", 47 | "webpack-cli": "^3.1.2", 48 | "webpack-dev-server": "^3.1.9" 49 | }, 50 | "scripts": { 51 | "build": "run-s -s clean build:*", 52 | "build:html": "appcache-manifest-fixer src/index.html -o dist/index.html", 53 | "build:js": "webpack --env.production --progress", 54 | "build:manifest": "appcache-manifest \"dist/**/*.{css,html,js,svg,eot,ttf,woff,woff2}\" --prefix /vue-eslint-demo --network-star -o dist/index.appcache", 55 | "build:versions": "node scripts/make-versions", 56 | "clean": "rimraf dist", 57 | "deploy": "node scripts/deploy", 58 | "lint": "eslint . --ext .js,.vue --rulesdir eslint-rules", 59 | "preversion": "run-s -s lint build", 60 | "postversion": "git push --tags && git push", 61 | "update-deps": "npm install eslint@latest eslint-plugin-vue@latest vue-eslint-parser@latest babel-eslint@latest", 62 | "watch": "run-p -s watch:*", 63 | "watch:html": "cpx src/index.html dist --watch", 64 | "watch:js": "webpack-dev-server --open --hot" 65 | }, 66 | "repository": { 67 | "type": "git", 68 | "url": "git+https://github.com/mysticatea/vue-eslint-demo.git" 69 | }, 70 | "keywords": [], 71 | "author": "Toru Nagashima (https://github.com/mysticatea)", 72 | "license": "MIT", 73 | "bugs": { 74 | "url": "https://github.com/mysticatea/vue-eslint-demo/issues" 75 | }, 76 | "homepage": "https://github.com/mysticatea/vue-eslint-demo#readme" 77 | } 78 | -------------------------------------------------------------------------------- /scripts/deploy.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const { spawn } = require("child_process") 4 | const path = require("path") 5 | const fs = require("fs-extra") 6 | const version = require("../package.json").version 7 | const ATOKEN = process.env.ATOKEN 8 | const BUILD_ROOT = path.resolve(__dirname, "../dist") 9 | const DEPLOY_ROOT = path.resolve(__dirname, "..") 10 | 11 | /** 12 | * Execute a command. 13 | * @param {string} command The command to execute. 14 | * @returns {void} 15 | */ 16 | function exec(command) { 17 | console.log(`> ${command}`) 18 | return new Promise((resolve, reject) => { 19 | const cp = spawn(command, [], { shell: true, stdio: "inherit" }) 20 | 21 | cp.on("close", code => { 22 | if (code) { 23 | reject(new Error(`Exited with ${code}.`)) 24 | } else { 25 | resolve() 26 | } 27 | }) 28 | }) 29 | } 30 | 31 | // Main 32 | ;(async () => { 33 | console.log("BUILD") 34 | await exec(`git checkout v${version}`) 35 | await exec("npm run -s build") 36 | 37 | console.log("LOAD GH-PAGES") 38 | if (ATOKEN) { 39 | await exec( 40 | "git fetch --depth=1 https://github.com/mysticatea/vue-eslint-demo.git gh-pages:gh-pages", 41 | ) 42 | await exec("git checkout gh-pages") 43 | } else { 44 | await exec("git checkout gh-pages") 45 | await exec("git pull") 46 | } 47 | 48 | console.log("CHECK VERSIONS") 49 | const oldVersions = await fs.readFile("versions.json", "utf8") 50 | const newVersions = await fs.readFile("dist/versions.json", "utf8") 51 | console.log(`OLD: ${oldVersions}`) 52 | console.log(`NEW: ${newVersions}`) 53 | 54 | // Deploy. 55 | if (newVersions !== oldVersions) { 56 | console.log("CLEAN") 57 | for (const filename of await fs.readdir(DEPLOY_ROOT)) { 58 | const stat = await fs.stat(filename) 59 | if (!stat.isFile() || filename.startsWith(".")) { 60 | continue 61 | } 62 | 63 | console.log(`> rm ${filename}`) 64 | await fs.unlink(filename) 65 | } 66 | 67 | console.log("DEPLOY") 68 | for (const filename of await fs.readdir(BUILD_ROOT)) { 69 | console.log(`> mv dist/${filename} ${filename}`) 70 | await fs.rename( 71 | path.join(BUILD_ROOT, filename), 72 | path.join(DEPLOY_ROOT, filename), 73 | ) 74 | } 75 | 76 | await exec("git add -A") 77 | let updated = false 78 | try { 79 | await exec('git commit -m "Update: website"') 80 | updated = true 81 | } catch (_error) { 82 | console.log("NO UPDATE") 83 | } 84 | if (updated) { 85 | await exec( 86 | `git push${ 87 | ATOKEN 88 | ? ` https://mysticatea:${ATOKEN}@github.com/mysticatea/vue-eslint-demo.git gh-pages:gh-pages` 89 | : "" 90 | }`, 91 | ) 92 | } 93 | } else { 94 | console.log("NO UPDATE") 95 | } 96 | 97 | // Back to master. 98 | await exec("git checkout master") 99 | console.log("COMPLETE") 100 | })().catch(error => { 101 | console.error(error.stack) 102 | process.exitCode = 1 103 | }) 104 | -------------------------------------------------------------------------------- /scripts/make-versions.js: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const fs = require("fs") 4 | 5 | fs.writeFileSync( 6 | "dist/versions.json", 7 | JSON.stringify({ 8 | "babel-eslint": require("babel-eslint/package.json").version, 9 | eslint: require("eslint/package.json").version, 10 | "eslint-plugin-vue": require("eslint-plugin-vue/package.json").version, 11 | typescript: require("typescript/package.json").version, 12 | "typescript-eslint-parser": require("typescript-eslint-parser/package.json") 13 | .version, 14 | "vue-eslint-demo": require("../package.json").version, 15 | "vue-eslint-parser": require("vue-eslint-parser/package.json").version, 16 | }), 17 | ) 18 | -------------------------------------------------------------------------------- /src/actions/edit-code.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Edit code. 3 | * @param {State} state The previous state 4 | * @param {string} code The new code. 5 | * @returns {State} The next state. 6 | */ 7 | export function editCode(state, code) { 8 | state.code = code 9 | } 10 | -------------------------------------------------------------------------------- /src/actions/index.js: -------------------------------------------------------------------------------- 1 | export * from "./edit-code" 2 | export * from "./select-indent-size" 3 | export * from "./select-indent-type" 4 | export * from "./select-parser" 5 | export * from "./select-rule-severity" 6 | -------------------------------------------------------------------------------- /src/actions/select-indent-size.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Select indent size. 3 | * @param {State} state The previous state 4 | * @param {number} indentSize The new indent size. 5 | * @returns {State} The next state. 6 | */ 7 | export function selectIndentSize(state, indentSize) { 8 | state.indentSize = indentSize 9 | } 10 | -------------------------------------------------------------------------------- /src/actions/select-indent-type.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Select indent type. 3 | * @param {State} state The previous state 4 | * @param {string} indentType The new indent type. 5 | * @returns {State} The next state. 6 | */ 7 | export function selectIndentType(state, indentType) { 8 | state.indentType = indentType 9 | } 10 | -------------------------------------------------------------------------------- /src/actions/select-parser.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Select parser. 3 | * @param {State} state The previous state 4 | * @param {string} parserId The new parser ID. 5 | * @returns {State} The next state. 6 | */ 7 | export function selectParser(state, parserId) { 8 | state.config.parserOptions.parser = parserId 9 | } 10 | -------------------------------------------------------------------------------- /src/actions/select-rule-severity.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Select rule's severity. 3 | * @param {State} state The previous state 4 | * @param {string|string[]} ruleIdOrRuleIds The rule ID to update severity. 5 | * @param {number} severity The new severity. 6 | * @returns {State} The next state. 7 | */ 8 | export function selectRuleSeverity(state, ruleIdOrRuleIds, severity) { 9 | const severityMap = state.config.rules 10 | 11 | if (Array.isArray(ruleIdOrRuleIds)) { 12 | for (const ruleId of ruleIdOrRuleIds) { 13 | severityMap[ruleId] = severity 14 | } 15 | } else { 16 | const ruleId = ruleIdOrRuleIds 17 | severityMap[ruleId] = severity 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/app-footer.vue: -------------------------------------------------------------------------------- 1 | 15 | 16 | 28 | 29 | 45 | -------------------------------------------------------------------------------- /src/app-header.vue: -------------------------------------------------------------------------------- 1 | 33 | 34 | 77 | 78 | 103 | -------------------------------------------------------------------------------- /src/app.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 89 | 90 | 134 | -------------------------------------------------------------------------------- /src/appcache-toast.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 26 | 27 | 61 | -------------------------------------------------------------------------------- /src/configuration-category-header.vue: -------------------------------------------------------------------------------- 1 | 16 | 17 | 59 | 60 | 87 | -------------------------------------------------------------------------------- /src/configuration-category-item.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 65 | 66 | 101 | -------------------------------------------------------------------------------- /src/configuration-category.vue: -------------------------------------------------------------------------------- 1 | 31 | 32 | 94 | 95 | 109 | -------------------------------------------------------------------------------- /src/configuration-parser-select.vue: -------------------------------------------------------------------------------- 1 | 9 | 10 | 67 | -------------------------------------------------------------------------------- /src/configuration-rules-select.vue: -------------------------------------------------------------------------------- 1 | 12 | 13 | 82 | -------------------------------------------------------------------------------- /src/configuration.vue: -------------------------------------------------------------------------------- 1 | 14 | 15 | 41 | 42 | 51 | -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Vue.js ESLint Demo 7 | 8 | 114 | 115 | 116 | 117 |
118 |
119 |
120 |
121 |
122 |
123 |
124 |
125 |
126 |
127 | Now loading... 128 |
129 |
130 | 160 | 161 | 164 | 165 | 166 | 167 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import "@babel/polyfill" 2 | import Vue from "vue" 3 | import App from "./app.vue" 4 | 5 | const app = new Vue({ 6 | el: "#main", 7 | render: h => h(App, { ref: "app" }), 8 | mounted() { 9 | window.MainContent.show() 10 | }, 11 | methods: { 12 | showUpdateReadyToast() { 13 | this.$refs.app.showUpdateReadyToast = true 14 | }, 15 | }, 16 | }) 17 | 18 | // Check update. 19 | window.applicationCache.addEventListener("updateready", () => { 20 | app.showUpdateReadyToast() 21 | }) 22 | -------------------------------------------------------------------------------- /src/lib/clone.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Clone a given value deeply. 3 | * @param {any} x The value to clone. 4 | * @returns {any} The cloned value. 5 | */ 6 | export function clone(x) { 7 | if (typeof x !== "object" || x === null) { 8 | return x 9 | } 10 | if (Array.isArray(x)) { 11 | return x.slice() 12 | } 13 | 14 | const y = {} 15 | for (const key of Object.keys(x)) { 16 | if (key !== "__proto__") { 17 | y[key] = clone(x[key]) 18 | } 19 | } 20 | return y 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/eslint.js: -------------------------------------------------------------------------------- 1 | import Linter from "eslint4b" 2 | import plugin from "eslint-plugin-vue" 3 | import * as parser from "vue-eslint-parser" 4 | 5 | const verifyOptions = Object.freeze({ 6 | preprocess: plugin.processors[".vue"].preprocess, 7 | postprocess: plugin.processors[".vue"].postprocess, 8 | }) 9 | 10 | export const linter = new class extends Linter { 11 | /** Initialize this linter. */ 12 | constructor() { 13 | super() 14 | this.defineParser("vue-eslint-parser", parser) 15 | for (const name of Object.keys(plugin.rules)) { 16 | this.defineRule(`vue/${name}`, plugin.rules[name]) 17 | } 18 | } 19 | 20 | /** @inheritdoc */ 21 | verify(textOrSourceCode, config, options) { 22 | return super.verify(textOrSourceCode, config, { 23 | ...options, 24 | ...verifyOptions, 25 | }) 26 | } 27 | 28 | /** @inheritdoc */ 29 | verifyAndFix(text, config, options) { 30 | return super.verifyAndFix(text, config, { 31 | ...options, 32 | ...verifyOptions, 33 | }) 34 | } 35 | }() 36 | -------------------------------------------------------------------------------- /src/lib/get-rule-url.js: -------------------------------------------------------------------------------- 1 | import { linter } from "./eslint" 2 | 3 | /** 4 | * Get the document URL of a rule. 5 | * @param {string} ruleId The rule ID to get. 6 | * @returns {string|null} The document URL of the rule. 7 | */ 8 | export function getRuleUrl(ruleId) { 9 | const rule = linter.getRules().get(ruleId) 10 | const meta = rule && rule.meta 11 | const docs = meta && meta.docs 12 | const url = docs && docs.url 13 | return url || null 14 | } 15 | -------------------------------------------------------------------------------- /src/lib/rule-categories.js: -------------------------------------------------------------------------------- 1 | import { linter } from "./eslint" 2 | 3 | // Initialize the categories of rules. 4 | /** @type {{name:string,rules:{name:string,description:string,fixable:boolean}[]}[]} */ 5 | export const ruleCategories = (() => { 6 | const ruleMap = linter.getRules() 7 | const categoryMap = { 8 | essential: { 9 | name: "Priority A: Essential", 10 | rules: [], 11 | }, 12 | "strongly-recommended": { 13 | name: "Priority B: Strongly Recommended", 14 | rules: [], 15 | }, 16 | recommended: { 17 | name: "Priority C: Recommended", 18 | rules: [], 19 | }, 20 | "use-with-caution": { 21 | name: "Priority D: Use with Caution", 22 | rules: [], 23 | }, 24 | uncategorized: { 25 | name: "Uncategorized", 26 | rules: [], 27 | }, 28 | base: { 29 | name: "Base Rules", 30 | rules: [], 31 | }, 32 | core: { 33 | name: "ESLint Core Rules", 34 | rules: [], 35 | }, 36 | } 37 | for (const entry of ruleMap) { 38 | const name = entry[0] 39 | const meta = entry[1].meta 40 | if (meta == null || meta.docs == null || meta.deprecated) { 41 | continue 42 | } 43 | const category = name.startsWith("vue/") 44 | ? categoryMap[meta.docs.category] || categoryMap.uncategorized 45 | : categoryMap.core 46 | 47 | category.rules.push({ 48 | name, 49 | description: meta.docs.description || "no description", 50 | url: meta.docs.url, 51 | fixable: Boolean(meta.fixable), 52 | }) 53 | } 54 | 55 | return Object.keys(categoryMap).map(id => categoryMap[id]) 56 | })() 57 | -------------------------------------------------------------------------------- /src/md-icon.vue: -------------------------------------------------------------------------------- 1 | 6 | 7 | 22 | 23 | 31 | -------------------------------------------------------------------------------- /src/message-list.vue: -------------------------------------------------------------------------------- 1 | 30 | 31 | 59 | 60 | 79 | -------------------------------------------------------------------------------- /src/playground.vue: -------------------------------------------------------------------------------- 1 | 20 | 21 | 112 | 113 | 129 | -------------------------------------------------------------------------------- /src/state/default-code.js: -------------------------------------------------------------------------------- 1 | export default ` 2 | 14 | 32 | `.trim() 33 | -------------------------------------------------------------------------------- /src/state/default-config.js: -------------------------------------------------------------------------------- 1 | import { linter } from "../lib/eslint.js" 2 | 3 | export default Object.freeze({ 4 | globals: Object.freeze({ 5 | Array: false, 6 | ArrayBuffer: false, 7 | Boolean: false, 8 | constructor: false, 9 | DataView: false, 10 | Date: false, 11 | decodeURI: false, 12 | decodeURIComponent: false, 13 | encodeURI: false, 14 | encodeURIComponent: false, 15 | Error: false, 16 | escape: false, 17 | eval: false, 18 | EvalError: false, 19 | Float32Array: false, 20 | Float64Array: false, 21 | Function: false, 22 | hasOwnProperty: false, 23 | Infinity: false, 24 | Int16Array: false, 25 | Int32Array: false, 26 | Int8Array: false, 27 | isFinite: false, 28 | isNaN: false, 29 | isPrototypeOf: false, 30 | JSON: false, 31 | Map: false, 32 | Math: false, 33 | NaN: false, 34 | Number: false, 35 | Object: false, 36 | parseFloat: false, 37 | parseInt: false, 38 | Promise: false, 39 | propertyIsEnumerable: false, 40 | Proxy: false, 41 | RangeError: false, 42 | ReferenceError: false, 43 | Reflect: false, 44 | RegExp: false, 45 | Set: false, 46 | String: false, 47 | Symbol: false, 48 | SyntaxError: false, 49 | toLocaleString: false, 50 | toString: false, 51 | TypeError: false, 52 | Uint16Array: false, 53 | Uint32Array: false, 54 | Uint8Array: false, 55 | Uint8ClampedArray: false, 56 | undefined: false, 57 | unescape: false, 58 | URIError: false, 59 | valueOf: false, 60 | WeakMap: false, 61 | WeakSet: false, 62 | AutocompleteErrorEvent: false, 63 | CDATASection: false, 64 | ClientRect: false, 65 | ClientRectList: false, 66 | CSSAnimation: false, 67 | CSSTransition: false, 68 | CSSUnknownRule: false, 69 | CSSViewportRule: false, 70 | Debug: false, 71 | DocumentTimeline: false, 72 | DOMSettableTokenList: false, 73 | ElementTimeControl: false, 74 | FederatedCredential: false, 75 | FileError: false, 76 | HTMLAppletElement: false, 77 | HTMLBlockquoteElement: false, 78 | HTMLIsIndexElement: false, 79 | HTMLKeygenElement: false, 80 | HTMLLayerElement: false, 81 | IDBEnvironment: false, 82 | InputMethodContext: false, 83 | MediaKeyError: false, 84 | MediaKeyEvent: false, 85 | MediaKeys: false, 86 | opera: false, 87 | PasswordCredential: false, 88 | ReadableByteStream: false, 89 | SharedKeyframeList: false, 90 | showModalDialog: false, 91 | SiteBoundCredential: false, 92 | SVGAltGlyphDefElement: false, 93 | SVGAltGlyphElement: false, 94 | SVGAltGlyphItemElement: false, 95 | SVGAnimateColorElement: false, 96 | SVGAnimatedPathData: false, 97 | SVGAnimatedPoints: false, 98 | SVGColor: false, 99 | SVGColorProfileElement: false, 100 | SVGColorProfileRule: false, 101 | SVGCSSRule: false, 102 | SVGCursorElement: false, 103 | SVGDocument: false, 104 | SVGElementInstance: false, 105 | SVGElementInstanceList: false, 106 | SVGEvent: false, 107 | SVGExternalResourcesRequired: false, 108 | SVGFilterPrimitiveStandardAttributes: false, 109 | SVGFitToViewBox: false, 110 | SVGFontElement: false, 111 | SVGFontFaceElement: false, 112 | SVGFontFaceFormatElement: false, 113 | SVGFontFaceNameElement: false, 114 | SVGFontFaceSrcElement: false, 115 | SVGFontFaceUriElement: false, 116 | SVGGlyphElement: false, 117 | SVGGlyphRefElement: false, 118 | SVGHKernElement: false, 119 | SVGICCColor: false, 120 | SVGLangSpace: false, 121 | SVGLocatable: false, 122 | SVGMissingGlyphElement: false, 123 | SVGPaint: false, 124 | SVGPathSeg: false, 125 | SVGPathSegArcAbs: false, 126 | SVGPathSegArcRel: false, 127 | SVGPathSegClosePath: false, 128 | SVGPathSegCurvetoCubicAbs: false, 129 | SVGPathSegCurvetoCubicRel: false, 130 | SVGPathSegCurvetoCubicSmoothAbs: false, 131 | SVGPathSegCurvetoCubicSmoothRel: false, 132 | SVGPathSegCurvetoQuadraticAbs: false, 133 | SVGPathSegCurvetoQuadraticRel: false, 134 | SVGPathSegCurvetoQuadraticSmoothAbs: false, 135 | SVGPathSegCurvetoQuadraticSmoothRel: false, 136 | SVGPathSegLinetoAbs: false, 137 | SVGPathSegLinetoHorizontalAbs: false, 138 | SVGPathSegLinetoHorizontalRel: false, 139 | SVGPathSegLinetoRel: false, 140 | SVGPathSegLinetoVerticalAbs: false, 141 | SVGPathSegLinetoVerticalRel: false, 142 | SVGPathSegList: false, 143 | SVGPathSegMovetoAbs: false, 144 | SVGPathSegMovetoRel: false, 145 | SVGRenderingIntent: false, 146 | SVGStylable: false, 147 | SVGTests: false, 148 | SVGTransformable: false, 149 | SVGTRefElement: false, 150 | SVGURIReference: false, 151 | SVGViewSpec: false, 152 | SVGVKernElement: false, 153 | SVGZoomAndPan: false, 154 | SVGZoomEvent: false, 155 | TimeEvent: false, 156 | XDomainRequest: false, 157 | XMLHttpRequestProgressEvent: false, 158 | XPathException: false, 159 | XPathNamespace: false, 160 | XPathNSResolver: false, 161 | addEventListener: false, 162 | alert: false, 163 | AnalyserNode: false, 164 | Animation: false, 165 | AnimationEffectReadOnly: false, 166 | AnimationEffectTiming: false, 167 | AnimationEffectTimingReadOnly: false, 168 | AnimationEvent: false, 169 | AnimationPlaybackEvent: false, 170 | AnimationTimeline: false, 171 | applicationCache: false, 172 | ApplicationCache: false, 173 | ApplicationCacheErrorEvent: false, 174 | atob: false, 175 | Attr: false, 176 | Audio: false, 177 | AudioBuffer: false, 178 | AudioBufferSourceNode: false, 179 | AudioContext: false, 180 | AudioDestinationNode: false, 181 | AudioListener: false, 182 | AudioNode: false, 183 | AudioParam: false, 184 | AudioProcessingEvent: false, 185 | AudioScheduledSourceNode: false, 186 | BarProp: false, 187 | BaseAudioContext: false, 188 | BatteryManager: false, 189 | BeforeUnloadEvent: false, 190 | BiquadFilterNode: false, 191 | Blob: false, 192 | blur: false, 193 | btoa: false, 194 | BlobEvent: false, 195 | BroadcastChannel: false, 196 | BudgetService: false, 197 | ByteLengthQueuingStrategy: false, 198 | Cache: false, 199 | caches: false, 200 | CacheStorage: false, 201 | cancelAnimationFrame: false, 202 | cancelIdleCallback: false, 203 | CanvasCaptureMediaStreamTrack: false, 204 | CanvasGradient: false, 205 | CanvasPattern: false, 206 | CanvasRenderingContext2D: false, 207 | ChannelMergerNode: false, 208 | ChannelSplitterNode: false, 209 | CharacterData: false, 210 | clearInterval: false, 211 | clearTimeout: false, 212 | clientInformation: false, 213 | ClipboardEvent: false, 214 | close: false, 215 | closed: false, 216 | CloseEvent: false, 217 | Comment: false, 218 | CompositionEvent: false, 219 | confirm: false, 220 | console: false, 221 | ConstantSourceNode: false, 222 | ConvolverNode: false, 223 | createImageBitmap: false, 224 | Credential: false, 225 | CredentialsContainer: false, 226 | CountQueuingStrategy: false, 227 | Crypto: false, 228 | crypto: false, 229 | CryptoKey: false, 230 | CSS: false, 231 | CSSConditionRule: false, 232 | CSSFontFaceRule: false, 233 | CSSGroupingRule: false, 234 | CSSImportRule: false, 235 | CSSKeyframeRule: false, 236 | CSSKeyframesRule: false, 237 | CSSMediaRule: false, 238 | CSSNamespaceRule: false, 239 | CSSPageRule: false, 240 | CSSRule: false, 241 | CSSRuleList: false, 242 | CSSStyleDeclaration: false, 243 | CSSStyleRule: false, 244 | CSSStyleSheet: false, 245 | CSSSupportsRule: false, 246 | CustomElementRegistry: false, 247 | customElements: false, 248 | CustomEvent: false, 249 | DataTransfer: false, 250 | DataTransferItem: false, 251 | DataTransferItemList: false, 252 | defaultStatus: false, 253 | defaultstatus: false, 254 | DelayNode: false, 255 | DeviceMotionEvent: false, 256 | DeviceOrientationEvent: false, 257 | devicePixelRatio: false, 258 | dispatchEvent: false, 259 | document: false, 260 | Document: false, 261 | DocumentFragment: false, 262 | DocumentType: false, 263 | DOMError: false, 264 | DOMException: false, 265 | DOMImplementation: false, 266 | DOMMatrix: false, 267 | DOMMatrixReadOnly: false, 268 | DOMParser: false, 269 | DOMPoint: false, 270 | DOMPointReadOnly: false, 271 | DOMQuad: false, 272 | DOMRect: false, 273 | DOMRectReadOnly: false, 274 | DOMStringList: false, 275 | DOMStringMap: false, 276 | DOMTokenList: false, 277 | DragEvent: false, 278 | DynamicsCompressorNode: false, 279 | Element: false, 280 | ErrorEvent: false, 281 | event: false, 282 | Event: false, 283 | EventSource: false, 284 | EventTarget: false, 285 | external: false, 286 | fetch: false, 287 | File: false, 288 | FileList: false, 289 | FileReader: false, 290 | find: false, 291 | focus: false, 292 | FocusEvent: false, 293 | FontFace: false, 294 | FontFaceSetLoadEvent: false, 295 | FormData: false, 296 | frameElement: false, 297 | frames: false, 298 | GainNode: false, 299 | Gamepad: false, 300 | GamepadButton: false, 301 | GamepadEvent: false, 302 | getComputedStyle: false, 303 | getSelection: false, 304 | HashChangeEvent: false, 305 | Headers: false, 306 | history: false, 307 | History: false, 308 | HTMLAllCollection: false, 309 | HTMLAnchorElement: false, 310 | HTMLAreaElement: false, 311 | HTMLAudioElement: false, 312 | HTMLBaseElement: false, 313 | HTMLBodyElement: false, 314 | HTMLBRElement: false, 315 | HTMLButtonElement: false, 316 | HTMLCanvasElement: false, 317 | HTMLCollection: false, 318 | HTMLContentElement: false, 319 | HTMLDataElement: false, 320 | HTMLDataListElement: false, 321 | HTMLDetailsElement: false, 322 | HTMLDialogElement: false, 323 | HTMLDirectoryElement: false, 324 | HTMLDivElement: false, 325 | HTMLDListElement: false, 326 | HTMLDocument: false, 327 | HTMLElement: false, 328 | HTMLEmbedElement: false, 329 | HTMLFieldSetElement: false, 330 | HTMLFontElement: false, 331 | HTMLFormControlsCollection: false, 332 | HTMLFormElement: false, 333 | HTMLFrameElement: false, 334 | HTMLFrameSetElement: false, 335 | HTMLHeadElement: false, 336 | HTMLHeadingElement: false, 337 | HTMLHRElement: false, 338 | HTMLHtmlElement: false, 339 | HTMLIFrameElement: false, 340 | HTMLImageElement: false, 341 | HTMLInputElement: false, 342 | HTMLLabelElement: false, 343 | HTMLLegendElement: false, 344 | HTMLLIElement: false, 345 | HTMLLinkElement: false, 346 | HTMLMapElement: false, 347 | HTMLMarqueeElement: false, 348 | HTMLMediaElement: false, 349 | HTMLMenuElement: false, 350 | HTMLMetaElement: false, 351 | HTMLMeterElement: false, 352 | HTMLModElement: false, 353 | HTMLObjectElement: false, 354 | HTMLOListElement: false, 355 | HTMLOptGroupElement: false, 356 | HTMLOptionElement: false, 357 | HTMLOptionsCollection: false, 358 | HTMLOutputElement: false, 359 | HTMLParagraphElement: false, 360 | HTMLParamElement: false, 361 | HTMLPictureElement: false, 362 | HTMLPreElement: false, 363 | HTMLProgressElement: false, 364 | HTMLQuoteElement: false, 365 | HTMLScriptElement: false, 366 | HTMLSelectElement: false, 367 | HTMLShadowElement: false, 368 | HTMLSlotElement: false, 369 | HTMLSourceElement: false, 370 | HTMLSpanElement: false, 371 | HTMLStyleElement: false, 372 | HTMLTableCaptionElement: false, 373 | HTMLTableCellElement: false, 374 | HTMLTableColElement: false, 375 | HTMLTableElement: false, 376 | HTMLTableRowElement: false, 377 | HTMLTableSectionElement: false, 378 | HTMLTemplateElement: false, 379 | HTMLTextAreaElement: false, 380 | HTMLTimeElement: false, 381 | HTMLTitleElement: false, 382 | HTMLTrackElement: false, 383 | HTMLUListElement: false, 384 | HTMLUnknownElement: false, 385 | HTMLVideoElement: false, 386 | IDBCursor: false, 387 | IDBCursorWithValue: false, 388 | IDBDatabase: false, 389 | IDBFactory: false, 390 | IDBIndex: false, 391 | IDBKeyRange: false, 392 | IDBObjectStore: false, 393 | IDBOpenDBRequest: false, 394 | IDBRequest: false, 395 | IDBTransaction: false, 396 | IDBVersionChangeEvent: false, 397 | IdleDeadline: false, 398 | IIRFilterNode: false, 399 | Image: false, 400 | ImageBitmap: false, 401 | ImageBitmapRenderingContext: false, 402 | ImageCapture: false, 403 | ImageData: false, 404 | indexedDB: false, 405 | innerHeight: false, 406 | innerWidth: false, 407 | InputEvent: false, 408 | IntersectionObserver: false, 409 | IntersectionObserverEntry: false, 410 | Intl: false, 411 | isSecureContext: false, 412 | KeyboardEvent: false, 413 | KeyframeEffect: false, 414 | KeyframeEffectReadOnly: false, 415 | length: false, 416 | localStorage: false, 417 | location: false, 418 | Location: false, 419 | locationbar: false, 420 | matchMedia: false, 421 | MediaDeviceInfo: false, 422 | MediaDevices: false, 423 | MediaElementAudioSourceNode: false, 424 | MediaEncryptedEvent: false, 425 | MediaError: false, 426 | MediaKeyMessageEvent: false, 427 | MediaKeySession: false, 428 | MediaKeyStatusMap: false, 429 | MediaKeySystemAccess: false, 430 | MediaList: false, 431 | MediaQueryList: false, 432 | MediaQueryListEvent: false, 433 | MediaSource: false, 434 | MediaRecorder: false, 435 | MediaSettingsRange: false, 436 | MediaStream: false, 437 | MediaStreamAudioDestinationNode: false, 438 | MediaStreamAudioSourceNode: false, 439 | MediaStreamEvent: false, 440 | MediaStreamTrack: false, 441 | MediaStreamTrackEvent: false, 442 | menubar: false, 443 | MessageChannel: false, 444 | MessageEvent: false, 445 | MessagePort: false, 446 | MIDIAccess: false, 447 | MIDIConnectionEvent: false, 448 | MIDIInput: false, 449 | MIDIInputMap: false, 450 | MIDIMessageEvent: false, 451 | MIDIOutput: false, 452 | MIDIOutputMap: false, 453 | MIDIPort: false, 454 | MimeType: false, 455 | MimeTypeArray: false, 456 | MouseEvent: false, 457 | moveBy: false, 458 | moveTo: false, 459 | MutationEvent: false, 460 | MutationObserver: false, 461 | MutationRecord: false, 462 | name: false, 463 | NamedNodeMap: false, 464 | NavigationPreloadManager: false, 465 | navigator: false, 466 | Navigator: false, 467 | NetworkInformation: false, 468 | Node: false, 469 | NodeFilter: false, 470 | NodeIterator: false, 471 | NodeList: false, 472 | Notification: false, 473 | OfflineAudioCompletionEvent: false, 474 | OfflineAudioContext: false, 475 | offscreenBuffering: false, 476 | onabort: true, 477 | onafterprint: true, 478 | onanimationend: true, 479 | onanimationiteration: true, 480 | onanimationstart: true, 481 | onappinstalled: true, 482 | onauxclick: true, 483 | onbeforeinstallprompt: true, 484 | onbeforeprint: true, 485 | onbeforeunload: true, 486 | onblur: true, 487 | oncancel: true, 488 | oncanplay: true, 489 | oncanplaythrough: true, 490 | onchange: true, 491 | onclick: true, 492 | onclose: true, 493 | oncontextmenu: true, 494 | oncuechange: true, 495 | ondblclick: true, 496 | ondevicemotion: true, 497 | ondeviceorientation: true, 498 | ondeviceorientationabsolute: true, 499 | ondrag: true, 500 | ondragend: true, 501 | ondragenter: true, 502 | ondragleave: true, 503 | ondragover: true, 504 | ondragstart: true, 505 | ondrop: true, 506 | ondurationchange: true, 507 | onemptied: true, 508 | onended: true, 509 | onerror: true, 510 | onfocus: true, 511 | ongotpointercapture: true, 512 | onhashchange: true, 513 | oninput: true, 514 | oninvalid: true, 515 | onkeydown: true, 516 | onkeypress: true, 517 | onkeyup: true, 518 | onlanguagechange: true, 519 | onload: true, 520 | onloadeddata: true, 521 | onloadedmetadata: true, 522 | onloadstart: true, 523 | onlostpointercapture: true, 524 | onmessage: true, 525 | onmessageerror: true, 526 | onmousedown: true, 527 | onmouseenter: true, 528 | onmouseleave: true, 529 | onmousemove: true, 530 | onmouseout: true, 531 | onmouseover: true, 532 | onmouseup: true, 533 | onmousewheel: true, 534 | onoffline: true, 535 | ononline: true, 536 | onpagehide: true, 537 | onpageshow: true, 538 | onpause: true, 539 | onplay: true, 540 | onplaying: true, 541 | onpointercancel: true, 542 | onpointerdown: true, 543 | onpointerenter: true, 544 | onpointerleave: true, 545 | onpointermove: true, 546 | onpointerout: true, 547 | onpointerover: true, 548 | onpointerup: true, 549 | onpopstate: true, 550 | onprogress: true, 551 | onratechange: true, 552 | onrejectionhandled: true, 553 | onreset: true, 554 | onresize: true, 555 | onscroll: true, 556 | onsearch: true, 557 | onseeked: true, 558 | onseeking: true, 559 | onselect: true, 560 | onstalled: true, 561 | onstorage: true, 562 | onsubmit: true, 563 | onsuspend: true, 564 | ontimeupdate: true, 565 | ontoggle: true, 566 | ontransitionend: true, 567 | onunhandledrejection: true, 568 | onunload: true, 569 | onvolumechange: true, 570 | onwaiting: true, 571 | onwheel: true, 572 | open: false, 573 | openDatabase: false, 574 | opener: false, 575 | Option: false, 576 | origin: false, 577 | OscillatorNode: false, 578 | outerHeight: false, 579 | outerWidth: false, 580 | PageTransitionEvent: false, 581 | pageXOffset: false, 582 | pageYOffset: false, 583 | PannerNode: false, 584 | parent: false, 585 | Path2D: false, 586 | PaymentAddress: false, 587 | PaymentRequest: false, 588 | PaymentRequestUpdateEvent: false, 589 | PaymentResponse: false, 590 | performance: false, 591 | Performance: false, 592 | PerformanceEntry: false, 593 | PerformanceLongTaskTiming: false, 594 | PerformanceMark: false, 595 | PerformanceMeasure: false, 596 | PerformanceNavigation: false, 597 | PerformanceNavigationTiming: false, 598 | PerformanceObserver: false, 599 | PerformanceObserverEntryList: false, 600 | PerformancePaintTiming: false, 601 | PerformanceResourceTiming: false, 602 | PerformanceTiming: false, 603 | PeriodicWave: false, 604 | Permissions: false, 605 | PermissionStatus: false, 606 | personalbar: false, 607 | PhotoCapabilities: false, 608 | Plugin: false, 609 | PluginArray: false, 610 | PointerEvent: false, 611 | PopStateEvent: false, 612 | postMessage: false, 613 | Presentation: false, 614 | PresentationAvailability: false, 615 | PresentationConnection: false, 616 | PresentationConnectionAvailableEvent: false, 617 | PresentationConnectionCloseEvent: false, 618 | PresentationConnectionList: false, 619 | PresentationReceiver: false, 620 | PresentationRequest: false, 621 | print: false, 622 | ProcessingInstruction: false, 623 | ProgressEvent: false, 624 | PromiseRejectionEvent: false, 625 | prompt: false, 626 | PushManager: false, 627 | PushSubscription: false, 628 | PushSubscriptionOptions: false, 629 | RadioNodeList: false, 630 | Range: false, 631 | ReadableStream: false, 632 | removeEventListener: false, 633 | RemotePlayback: false, 634 | Request: false, 635 | requestAnimationFrame: false, 636 | requestIdleCallback: false, 637 | resizeBy: false, 638 | ResizeObserver: false, 639 | ResizeObserverEntry: false, 640 | resizeTo: false, 641 | Response: false, 642 | RTCCertificate: false, 643 | RTCDataChannel: false, 644 | RTCDataChannelEvent: false, 645 | RTCIceCandidate: false, 646 | RTCPeerConnection: false, 647 | RTCPeerConnectionIceEvent: false, 648 | RTCRtpContributingSource: false, 649 | RTCRtpReceiver: false, 650 | RTCSessionDescription: false, 651 | RTCStatsReport: false, 652 | screen: false, 653 | Screen: false, 654 | screenLeft: false, 655 | ScreenOrientation: false, 656 | screenTop: false, 657 | screenX: false, 658 | screenY: false, 659 | ScriptProcessorNode: false, 660 | scroll: false, 661 | scrollbars: false, 662 | scrollBy: false, 663 | scrollTo: false, 664 | scrollX: false, 665 | scrollY: false, 666 | SecurityPolicyViolationEvent: false, 667 | Selection: false, 668 | self: false, 669 | ServiceWorker: false, 670 | ServiceWorkerContainer: false, 671 | ServiceWorkerRegistration: false, 672 | sessionStorage: false, 673 | setInterval: false, 674 | setTimeout: false, 675 | ShadowRoot: false, 676 | SharedWorker: false, 677 | SourceBuffer: false, 678 | SourceBufferList: false, 679 | speechSynthesis: false, 680 | SpeechSynthesisEvent: false, 681 | SpeechSynthesisUtterance: false, 682 | StaticRange: false, 683 | status: false, 684 | statusbar: false, 685 | StereoPannerNode: false, 686 | stop: false, 687 | Storage: false, 688 | StorageEvent: false, 689 | StorageManager: false, 690 | styleMedia: false, 691 | StyleSheet: false, 692 | StyleSheetList: false, 693 | SubtleCrypto: false, 694 | SVGAElement: false, 695 | SVGAngle: false, 696 | SVGAnimatedAngle: false, 697 | SVGAnimatedBoolean: false, 698 | SVGAnimatedEnumeration: false, 699 | SVGAnimatedInteger: false, 700 | SVGAnimatedLength: false, 701 | SVGAnimatedLengthList: false, 702 | SVGAnimatedNumber: false, 703 | SVGAnimatedNumberList: false, 704 | SVGAnimatedPreserveAspectRatio: false, 705 | SVGAnimatedRect: false, 706 | SVGAnimatedString: false, 707 | SVGAnimatedTransformList: false, 708 | SVGAnimateElement: false, 709 | SVGAnimateMotionElement: false, 710 | SVGAnimateTransformElement: false, 711 | SVGAnimationElement: false, 712 | SVGCircleElement: false, 713 | SVGClipPathElement: false, 714 | SVGComponentTransferFunctionElement: false, 715 | SVGDefsElement: false, 716 | SVGDescElement: false, 717 | SVGDiscardElement: false, 718 | SVGElement: false, 719 | SVGEllipseElement: false, 720 | SVGFEBlendElement: false, 721 | SVGFEColorMatrixElement: false, 722 | SVGFEComponentTransferElement: false, 723 | SVGFECompositeElement: false, 724 | SVGFEConvolveMatrixElement: false, 725 | SVGFEDiffuseLightingElement: false, 726 | SVGFEDisplacementMapElement: false, 727 | SVGFEDistantLightElement: false, 728 | SVGFEDropShadowElement: false, 729 | SVGFEFloodElement: false, 730 | SVGFEFuncAElement: false, 731 | SVGFEFuncBElement: false, 732 | SVGFEFuncGElement: false, 733 | SVGFEFuncRElement: false, 734 | SVGFEGaussianBlurElement: false, 735 | SVGFEImageElement: false, 736 | SVGFEMergeElement: false, 737 | SVGFEMergeNodeElement: false, 738 | SVGFEMorphologyElement: false, 739 | SVGFEOffsetElement: false, 740 | SVGFEPointLightElement: false, 741 | SVGFESpecularLightingElement: false, 742 | SVGFESpotLightElement: false, 743 | SVGFETileElement: false, 744 | SVGFETurbulenceElement: false, 745 | SVGFilterElement: false, 746 | SVGForeignObjectElement: false, 747 | SVGGElement: false, 748 | SVGGeometryElement: false, 749 | SVGGradientElement: false, 750 | SVGGraphicsElement: false, 751 | SVGImageElement: false, 752 | SVGLength: false, 753 | SVGLengthList: false, 754 | SVGLinearGradientElement: false, 755 | SVGLineElement: false, 756 | SVGMarkerElement: false, 757 | SVGMaskElement: false, 758 | SVGMatrix: false, 759 | SVGMetadataElement: false, 760 | SVGMPathElement: false, 761 | SVGNumber: false, 762 | SVGNumberList: false, 763 | SVGPathElement: false, 764 | SVGPatternElement: false, 765 | SVGPoint: false, 766 | SVGPointList: false, 767 | SVGPolygonElement: false, 768 | SVGPolylineElement: false, 769 | SVGPreserveAspectRatio: false, 770 | SVGRadialGradientElement: false, 771 | SVGRect: false, 772 | SVGRectElement: false, 773 | SVGScriptElement: false, 774 | SVGSetElement: false, 775 | SVGStopElement: false, 776 | SVGStringList: false, 777 | SVGStyleElement: false, 778 | SVGSVGElement: false, 779 | SVGSwitchElement: false, 780 | SVGSymbolElement: false, 781 | SVGTextContentElement: false, 782 | SVGTextElement: false, 783 | SVGTextPathElement: false, 784 | SVGTextPositioningElement: false, 785 | SVGTitleElement: false, 786 | SVGTransform: false, 787 | SVGTransformList: false, 788 | SVGTSpanElement: false, 789 | SVGUnitTypes: false, 790 | SVGUseElement: false, 791 | SVGViewElement: false, 792 | TaskAttributionTiming: false, 793 | Text: false, 794 | TextDecoder: false, 795 | TextEncoder: false, 796 | TextEvent: false, 797 | TextMetrics: false, 798 | TextTrack: false, 799 | TextTrackCue: false, 800 | TextTrackCueList: false, 801 | TextTrackList: false, 802 | TimeRanges: false, 803 | toolbar: false, 804 | top: false, 805 | Touch: false, 806 | TouchEvent: false, 807 | TouchList: false, 808 | TrackEvent: false, 809 | TransitionEvent: false, 810 | TreeWalker: false, 811 | UIEvent: false, 812 | URL: false, 813 | URLSearchParams: false, 814 | ValidityState: false, 815 | VisualViewport: false, 816 | visualViewport: false, 817 | VTTCue: false, 818 | WaveShaperNode: false, 819 | WebAssembly: false, 820 | WebGL2RenderingContext: false, 821 | WebGLActiveInfo: false, 822 | WebGLBuffer: false, 823 | WebGLContextEvent: false, 824 | WebGLFramebuffer: false, 825 | WebGLProgram: false, 826 | WebGLQuery: false, 827 | WebGLRenderbuffer: false, 828 | WebGLRenderingContext: false, 829 | WebGLSampler: false, 830 | WebGLShader: false, 831 | WebGLShaderPrecisionFormat: false, 832 | WebGLSync: false, 833 | WebGLTexture: false, 834 | WebGLTransformFeedback: false, 835 | WebGLUniformLocation: false, 836 | WebGLVertexArrayObject: false, 837 | WebSocket: false, 838 | WheelEvent: false, 839 | Window: false, 840 | window: false, 841 | Worker: false, 842 | WritableStream: false, 843 | XMLDocument: false, 844 | XMLHttpRequest: false, 845 | XMLHttpRequestEventTarget: false, 846 | XMLHttpRequestUpload: false, 847 | XMLSerializer: false, 848 | XPathEvaluator: false, 849 | XPathExpression: false, 850 | XPathResult: false, 851 | XSLTProcessor: false, 852 | }), 853 | 854 | rules: Object.freeze( 855 | (() => { 856 | const rules = {} 857 | for (const [name, rule] of linter.getRules()) { 858 | const enabled = 859 | name.startsWith("vue/") || 860 | rule.meta.docs.recommended || 861 | name === "object-shorthand" 862 | rules[name] = enabled ? 2 : 0 863 | } 864 | return rules 865 | })(), 866 | ), 867 | 868 | parser: "vue-eslint-parser", 869 | parserOptions: Object.freeze({ 870 | parser: "espree", 871 | ecmaVersion: 2019, 872 | sourceType: "module", 873 | ecmaFeatures: Object.freeze({ jsx: true }), 874 | }), 875 | }) 876 | -------------------------------------------------------------------------------- /src/state/deserialize.js: -------------------------------------------------------------------------------- 1 | import pako from "pako" 2 | import { clone } from "../lib/clone" 3 | import { initialState } from "./initial-state" 4 | 5 | /*eslint-disable complexity */ 6 | /** 7 | * Deserialize a given serialized string then update this object. 8 | * @param {string} serializedString A serialized string. 9 | * @returns {object} The deserialized state. 10 | */ 11 | export function deserializeState(serializedString) { 12 | const state = clone(initialState) 13 | 14 | if (serializedString === "") { 15 | return state 16 | } 17 | 18 | try { 19 | // For backward compatibility, it can address non-compressed data. 20 | const compressed = !serializedString.startsWith("eyJj") 21 | const decodedText = atob(serializedString) 22 | const jsonText = compressed 23 | ? pako.inflate(decodedText, { to: "string" }) 24 | : decodedText 25 | const json = JSON.parse(jsonText) 26 | 27 | if (typeof json === "object" && json !== null) { 28 | if (typeof json.code === "string") { 29 | state.code = json.code 30 | } 31 | if (typeof json.rules === "object" && json.rules !== null) { 32 | for (const id of Object.keys(state.config.rules)) { 33 | state.config.rules[id] = json.rules[id] ? 2 : 0 34 | } 35 | } 36 | if ( 37 | json.indentSize === 2 || 38 | json.indentSize === 4 || 39 | json.indentSize === 8 40 | ) { 41 | state.indentSize = json.indentSize 42 | } 43 | if (json.indentType === "space" || json.indentType === "tab") { 44 | state.indentType = json.indentType 45 | } 46 | if ( 47 | json.parser === "espree" || 48 | json.parser === "babel-eslint" || 49 | json.parser === "typescript-eslint-parser" 50 | ) { 51 | state.config.parserOptions.parser = json.parser 52 | } 53 | } 54 | } catch (error) { 55 | console.error(error) 56 | } 57 | 58 | return state 59 | } 60 | /*eslint-enable complexity */ 61 | -------------------------------------------------------------------------------- /src/state/index.js: -------------------------------------------------------------------------------- 1 | export * from "./deserialize" 2 | export * from "./initial-state" 3 | export * from "./serialize" 4 | -------------------------------------------------------------------------------- /src/state/initial-state.js: -------------------------------------------------------------------------------- 1 | import defaultCode from "./default-code" 2 | import defaultConfig from "./default-config" 3 | export * from "../lib/eslint" 4 | 5 | /** 6 | * The initial state. 7 | */ 8 | export const initialState = Object.freeze({ 9 | code: defaultCode, 10 | config: defaultConfig, 11 | indentType: "space", 12 | indentSize: 2, 13 | }) 14 | -------------------------------------------------------------------------------- /src/state/serialize.js: -------------------------------------------------------------------------------- 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(allRules) { 9 | return Object.keys(allRules).reduce((map, id) => { 10 | if (allRules[id] === 2) { 11 | map[id] = 2 12 | } 13 | return map 14 | }, {}) 15 | } 16 | 17 | /** 18 | * Serialize a given state as a base64 string. 19 | * @param {State} state The state to serialize. 20 | * @returns {string} The serialized string. 21 | */ 22 | export function serializeState(state) { 23 | const jsonString = JSON.stringify({ 24 | code: state.code, 25 | rules: getEnabledRules(state.config.rules), 26 | indentSize: state.indentSize, 27 | indentType: state.indentType, 28 | parser: state.config.parserOptions.parser, 29 | }) 30 | const compressedString = pako.deflate(jsonString, { to: "string" }) 31 | const base64 = btoa(compressedString) 32 | 33 | //eslint-disable-next-line no-console 34 | console.log( 35 | `The compress rate of serialized string: ${( 36 | (100 * base64.length) / 37 | jsonString.length 38 | ).toFixed(1)}% (${jsonString.length}B → ${base64.length}B)`, 39 | ) 40 | 41 | return base64 42 | } 43 | -------------------------------------------------------------------------------- /src/versions.js: -------------------------------------------------------------------------------- 1 | // THE CONTENT OF THIS FILE WILL BE INJECTED IN BUILD SCRIPT. 2 | export default { 3 | "vue-eslint-demo": { 4 | repo: "mysticatea/vue-eslint-demo", 5 | version: "0.0.0", 6 | }, 7 | eslint: { 8 | repo: "eslint/eslint", 9 | version: "0.0.0", 10 | }, 11 | "eslint-plugin-vue": { 12 | repo: "vuejs/eslint-plugin-vue", 13 | version: "0.0.0", 14 | }, 15 | "vue-eslint-parser": { 16 | repo: "mysticatea/vue-eslint-parser", 17 | version: "0.0.0", 18 | }, 19 | "babel-eslint": { 20 | repo: "babel/babel-eslint", 21 | version: "0.0.0", 22 | }, 23 | "typescript-eslint-parser": { 24 | repo: "eslint/typescript-eslint-parser", 25 | version: "0.0.0", 26 | }, 27 | typescript: { 28 | repo: "Microsoft/typescript", 29 | version: "0.0.0", 30 | }, 31 | } 32 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | /*eslint-env node */ 2 | 3 | const path = require("path") 4 | const postcssPresetEnv = require("postcss-preset-env") 5 | const VueLoaderPlugin = require("vue-loader/lib/plugin") 6 | 7 | // Shim for `src/versions.js` 8 | const VERSIONS = `export default ${JSON.stringify({ 9 | "vue-eslint-demo": { 10 | repo: "mysticatea/vue-eslint-demo", 11 | version: require("./package.json").version, 12 | }, 13 | eslint: { 14 | repo: "eslint/eslint", 15 | version: require("eslint/package.json").version, 16 | }, 17 | "eslint-plugin-vue": { 18 | repo: "vuejs/eslint-plugin-vue", 19 | version: require("eslint-plugin-vue/package.json").version, 20 | }, 21 | "vue-eslint-parser": { 22 | repo: "mysticatea/vue-eslint-parser", 23 | version: require("vue-eslint-parser/package.json").version, 24 | }, 25 | "babel-eslint": { 26 | repo: "babel/babel-eslint", 27 | version: require("babel-eslint/package.json").version, 28 | }, 29 | "typescript-eslint-parser": { 30 | repo: "eslint/typescript-eslint-parser", 31 | version: require("typescript-eslint-parser/package.json").version, 32 | }, 33 | typescript: { 34 | repo: "Microsoft/typescript", 35 | version: require("typescript/package.json").version, 36 | }, 37 | })}` 38 | 39 | // Shim for vue-eslint-parser. 40 | const IMPORT_PARSER = `( 41 | parserOptions.parser === "babel-eslint" ? require("babel-eslint") : 42 | parserOptions.parser === "typescript-eslint-parser" ? require("typescript-eslint-parser") : 43 | /* otherwise */ require("espree") 44 | )` 45 | 46 | module.exports = env => { 47 | const prod = Boolean(env && env.production) 48 | const mode = prod ? "production" : "development" 49 | const browserlist = [">1%", "not dead", "not ie 11"] 50 | 51 | return { 52 | mode, 53 | target: "web", 54 | entry: "./src/index.js", 55 | output: { 56 | path: path.resolve(__dirname, "./dist"), 57 | filename: "index.js", 58 | }, 59 | module: { 60 | rules: [ 61 | { 62 | test: /\.vue$/, 63 | use: ["vue-loader"], 64 | }, 65 | { 66 | test: /\.m?js$/, 67 | exclude: /node_modules[\\/](?!vue-eslint-editor)/, 68 | use: [ 69 | { 70 | loader: "babel-loader", 71 | options: { 72 | babelrc: false, 73 | cacheDirectory: true, 74 | plugins: [ 75 | "@babel/plugin-syntax-dynamic-import", 76 | [ 77 | "@babel/plugin-transform-runtime", 78 | { useESModules: true }, 79 | ], 80 | ], 81 | presets: [ 82 | [ 83 | "@babel/preset-env", 84 | { 85 | modules: false, 86 | targets: browserlist, 87 | useBuiltIns: "entry", 88 | }, 89 | ], 90 | ], 91 | }, 92 | }, 93 | ], 94 | }, 95 | { 96 | test: /\.css$/, 97 | use: [ 98 | { 99 | loader: "vue-style-loader", 100 | options: {}, 101 | }, 102 | { 103 | loader: "css-loader", 104 | options: {}, 105 | }, 106 | { 107 | loader: "postcss-loader", 108 | options: { 109 | plugins: [ 110 | postcssPresetEnv({ 111 | browsers: browserlist, 112 | stage: 3, 113 | }), 114 | ], 115 | }, 116 | }, 117 | ], 118 | }, 119 | { 120 | test: /\.(png|jpg|gif|svg|eot|ijmap|ttf|woff2?)$/, 121 | use: [ 122 | { 123 | loader: "url-loader", 124 | options: { 125 | limit: 8192, 126 | }, 127 | }, 128 | ], 129 | }, 130 | // Replace `./src/versions.js` with the current versions. 131 | { 132 | test: /src[\\/]versions/, 133 | use: [ 134 | { 135 | loader: "string-replace-loader", 136 | options: { 137 | search: "[\\s\\S]+", // whole file. 138 | replace: VERSIONS, 139 | flags: "g", 140 | }, 141 | }, 142 | ], 143 | }, 144 | // `vue-eslint-parser` has `require(parserOptions.parser || "espree")`. 145 | // Modify it by a static importing. 146 | { 147 | test: /node_modules[/\\]vue-eslint-parser[/\\]index\.js$/, 148 | use: [ 149 | { 150 | loader: "string-replace-loader", 151 | options: { 152 | search: 153 | 'typeof parserOptions.parser === "string"\n ? require(parserOptions.parser)\n : require("espree")', 154 | replace: IMPORT_PARSER, 155 | }, 156 | }, 157 | ], 158 | }, 159 | // Patch for `babel-eslint` -- accessing `global` causes build error. 160 | { 161 | test: /node_modules[/\\]babel-eslint[/\\]lib[/\\]analyze-scope\.js$/, 162 | use: [ 163 | { 164 | loader: "string-replace-loader", 165 | options: { 166 | search: 'require("./patch-eslint-scope")', 167 | replace: "Object", 168 | }, 169 | }, 170 | ], 171 | }, 172 | // Patch for `eslint-utils` -- accessing `global` causes build error. 173 | { 174 | test: /node_modules[/\\]eslint-utils[/\\]index\.m?js$/, 175 | use: [ 176 | { 177 | loader: "string-replace-loader", 178 | options: { 179 | search: "\\bin global\\b", 180 | replace: "in window", 181 | flags: "g", 182 | }, 183 | }, 184 | { 185 | loader: "string-replace-loader", 186 | options: { 187 | search: "\\bglobal\\[", 188 | replace: "window[", 189 | flags: "g", 190 | }, 191 | }, 192 | ], 193 | }, 194 | // Patch for `typescript` 195 | { 196 | test: /node_modules[/\\]typescript[/\\]lib[/\\]typescript.js$/, 197 | use: [ 198 | { 199 | loader: "string-replace-loader", 200 | options: { 201 | search: "require\\(.+?\\)", 202 | replace: "null", 203 | flags: "g", 204 | }, 205 | }, 206 | ], 207 | }, 208 | ], 209 | }, 210 | resolve: { 211 | alias: { 212 | vue$: "vue/dist/vue.esm.js", 213 | }, 214 | extensions: [".mjs", ".js", ".vue", ".json"], 215 | }, 216 | plugins: [new VueLoaderPlugin()], 217 | devServer: { 218 | contentBase: path.join(__dirname, "dist"), 219 | compress: true, 220 | }, 221 | performance: { 222 | hints: false, 223 | }, 224 | devtool: false, 225 | } 226 | } 227 | --------------------------------------------------------------------------------