Last active
May 24, 2024 20:35
-
-
Save ShayanTheNerd/6c1ba67117b4bd9115fd48036316820b to your computer and use it in GitHub Desktop.
Exhaustive ESLint config for Vue.js, TypeScript, and Tailwind CSS
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import jsESLint from '@eslint/js'; | |
import vueESLint from 'eslint-plugin-vue'; | |
import antfuConfig from '@antfu/eslint-config'; | |
import htmlESLintParser from '@html-eslint/parser'; | |
import htmlESLint from '@html-eslint/eslint-plugin'; | |
import stylisticESLint from '@stylistic/eslint-plugin'; | |
/* eslint-disable no-magic-numbers -- Improve SNR */ | |
const jsRules = { | |
...jsESLint.configs.recommended.rules, | |
/* Possible Problems */ | |
'no-await-in-loop': 'error', | |
'no-self-compare': 'error', | |
'no-unreachable-loop': 'error', | |
'no-inner-declarations': 'error', | |
'array-callback-return': 'error', | |
'no-useless-assignment': 'error', | |
'no-constructor-return': 'error', | |
'require-atomic-updates': 'error', | |
'no-async-promise-executor': 'error', | |
'no-template-curly-in-string': 'error', | |
'no-promise-executor-return': 'error', | |
'no-unmodified-loop-condition': 'error', | |
'no-use-before-define': ['error', 'nofunc'], | |
'use-isnan': ['error', { enforceForIndexOf: true }], | |
'no-unused-vars': ['error', { argsIgnorePattern: '^_' }], | |
'no-unsafe-negation': ['error', { enforceForOrderingRelations: true }], | |
'no-unsafe-optional-chaining': ['error', { disallowArithmeticOperators: true }], | |
/* Suggestions */ | |
'yoda': 'error', | |
'strict': 'error', | |
'no-var': 'error', | |
'eqeqeq': 'error', | |
'no-new': 'error', | |
'no-eval': 'error', | |
'no-void': 'error', | |
'new-cap': 'error', | |
'no-proto': 'error', | |
'no-caller': 'error', | |
'no-empty': 'error', | |
'no-eq-null': 'error', | |
'camelcase': 'error', | |
'sort-imports': 'off', | |
'no-redeclare': 'off', | |
'complexity': 'error', | |
'no-plusplus': 'error', | |
'no-iterator': 'error', | |
'no-continue': 'error', | |
'no-lonely-if': 'error', | |
'max-params': 'error', | |
'no-shadow': ['error'], | |
'no-label-var': 'error', | |
'no-new-func': 'error', | |
'no-multi-str': 'error', | |
'guard-for-in': 'error', | |
'no-loop-func': 'error', | |
'default-case': 'error', | |
'no-script-url': 'error', | |
'prefer-const': 'error', | |
'no-undefined': 'error', | |
'no-undef-init': 'error', | |
'require-await': 'error', | |
'no-extra-bind': 'error', | |
'prefer-spread': 'error', | |
'no-lone-blocks': 'error', | |
'no-extra-label': 'error', | |
'no-invalid-this': 'error', | |
'accessor-pairs': 'error', | |
'no-useless-call': 'error', | |
'max-depth': ['error', 3], | |
'no-implied-eval': 'error', | |
'consistent-this': 'error', | |
'no-octal-escape': 'error', | |
'no-throw-literal': 'error', | |
'prefer-template': 'error', | |
'init-declarations': 'error', | |
'no-new-wrappers': 'error', | |
'block-scoped-var': 'error', | |
'no-extend-native': 'error', | |
'default-case-last': 'error', | |
'object-shorthand': 'error', | |
'no-useless-return': 'error', | |
'consistent-return': 'error', | |
'no-useless-concat': 'error', | |
'no-param-reassign': 'error', | |
'no-nested-ternary': 'error', | |
'no-useless-rename': 'error', | |
'no-implicit-globals': 'error', | |
'default-param-last': 'error', | |
'symbol-description': 'error', | |
'curly': ['error', 'multi-line'], | |
'prefer-rest-params': 'error', | |
'no-implicit-coercion': 'error', | |
'radix': ['error', 'as-needed'], | |
'func-name-matching': 'error', | |
'operator-assignment': 'error', | |
'no-unneeded-ternary': 'error', | |
'no-array-constructor': 'error', | |
'prefer-destructuring': 'error', | |
'no-underscore-dangle': 'error', | |
'prefer-object-spread': 'error', | |
'prefer-arrow-callback': 'error', | |
'no-object-constructor': 'error', | |
'prefer-object-has-own': 'error', | |
'no-useless-constructor': 'error', | |
'class-methods-use-this': 'error', | |
'require-unicode-regexp': 'error', | |
'prefer-numeric-literals': 'error', | |
'no-useless-computed-key': 'error', | |
'max-nested-callbacks': ['error', 3], | |
'no-return-assign': ['error', 'always'], | |
'prefer-named-capture-group': 'error', | |
'no-bitwise': ['error', { int32Hint: true }], | |
'prefer-exponentiation-operator': 'error', | |
'import/extensions': ['error', 'ignorePackages'], | |
'dot-notation': ['error', { allowKeywords: false }], | |
'logical-assignment-operators': ['error', 'always'], | |
'no-sequences': ['error', { allowInParentheses: false }], | |
'no-multi-assign': ['error', { ignoreNonDeclaration: true }], | |
'no-empty-function': ['error', { allow: ['arrowFunctions'] }], | |
'func-style': ['error', 'declaration', { allowArrowFunctions: true }], | |
'prefer-promise-reject-errors': ['error', { allowEmptyReject: true }], | |
'prefer-regex-literals': ['error', { disallowRedundantWrapping: true }], | |
'no-extra-boolean-cast': ['error', { enforceForLogicalOperands: true }], | |
'no-restricted-exports': ['error', { restrictedNamedExports: ['default'] }], | |
'no-unused-expressions': ['error', { allowShortCircuit: true, allowTernary: true }], | |
'arrow-body-style': ['error', 'as-needed', { requireReturnForObjectLiteral: true }], | |
'no-magic-numbers': ['error', { | |
enforceConst: true, | |
detectObjects: true, | |
ignoreDefaultValues: true, | |
ignoreClassFieldInitialValues: true, | |
ignore: [-1, 0, 1, 100], | |
}], | |
}; | |
const tsRules = { | |
'ts/no-misused-promises': 'off', | |
'ts/no-unsafe-assignment': 'off', | |
'ts/member-delimiter-style': 'error', | |
'ts/type-annotation-spacing': 'error', | |
'ts/consistent-type-definitions': ['error', 'type'], | |
}; | |
const vueRules = { | |
...vueESLint.configs.recommended.rules, | |
/* Base */ | |
'vue/jsx-uses-vars': 'error', | |
'vue/comment-directive': ['error', { reportUnusedDisableDirectives: true }], | |
/* Priority B: Strongly Recommended (Improving Readability) */ | |
'vue/singleline-html-element-content-newline': 'off', | |
'vue/max-attributes-per-line': ['error', { singleline: 3 }], | |
'vue/v-slot-style': ['error', { atComponent: 'shorthand' }], | |
'vue/html-self-closing': ['error', { html: { void: 'always' } }], | |
'vue/first-attribute-linebreak': ['error', { singleline: 'beside' }], | |
'vue/html-indent': ['error', 'tab', { attribute: 1, baseIndent: 1 }], | |
'vue/v-on-event-hyphenation': ['error', 'always', { autofix: true }], | |
'vue/v-bind-style': ['error', 'shorthand', { sameNameShorthand: 'always' }], | |
/* Priority C: Recommended (Potentially Dangerous Patterns) */ | |
'vue/attributes-order': ['error', { | |
order: [ | |
'RENDER_MODIFIERS', | |
'DEFINITION', | |
'CONDITIONALS', | |
'LIST_RENDERING', | |
'UNIQUE', | |
'TWO_WAY_BINDING', | |
'OTHER_DIRECTIVES', | |
'OTHER_ATTR', | |
'SLOT', | |
'CONTENT', | |
'GLOBAL', | |
], | |
}], | |
/* Miscellaneous */ | |
'vue/no-root-v-if': ['error'], | |
'vue/require-expose': ['error'], | |
'vue/no-useless-v-bind': ['error'], | |
'vue/require-typed-ref': ['error'], | |
'vue/valid-define-options': ['error'], | |
'vue/require-explicit-slots': ['error'], | |
'vue/prefer-define-options': ['error'], | |
'vue/require-emit-validator': ['error'], | |
'vue/no-use-v-else-with-v-for': ['error'], | |
'vue/v-for-delimiter-style': ['error', 'in'], | |
'vue/no-empty-component-block': ['error'], | |
'vue/html-comment-indent': ['error', 'tab'], | |
'vue/no-multiple-objects-in-class': ['error'], | |
'vue/prefer-separate-static-class': ['error'], | |
'vue/no-ref-object-reactivity-loss': ['error'], | |
'vue/no-duplicate-attr-inheritance': ['error'], | |
'vue/no-this-in-before-route-enter': ['error'], | |
'vue/no-setup-props-reactivity-loss': ['error'], | |
'vue/block-lang': ['error', { script: { lang: 'ts' } }], | |
'vue/component-api-style': ['error', ['script-setup']], | |
'vue/padding-line-between-blocks': ['error', 'always'], | |
'vue/define-props-declaration': ['error', 'type-based'], | |
'vue/custom-event-name-casing': ['error', 'camelCase'], | |
'vue/define-emits-declaration': ['error', 'type-literal'], | |
'vue/html-comment-content-spacing': ['error', 'always'], | |
'vue/prefer-true-attribute-shorthand': ['error', 'always'], | |
'vue/no-static-inline-styles': ['error', { allowBinding: true }], | |
'vue/enforce-style-attribute': ['error', { allow: ['scoped'] }], | |
'vue/component-options-name-casing': ['error', 'PascalCase'], | |
'vue/no-unsupported-features': ['error', { version: '^3.4.21' }], | |
'vue/no-required-prop-with-default': ['error', { autofix: true }], | |
'vue/v-on-handler-style': ['error', ['method', 'inline-function']], | |
'vue/no-deprecated-model-definition': ['error', { allowVue3Compat: true }], | |
'vue/html-button-has-type': ['error', { button: true, submit: true, reset: true }], | |
'vue/block-order': ['error', { order: ['script[setup]', 'template', 'style[scoped]'] }], | |
'vue/html-comment-content-newline': ['error', { singleline: 'never', multiline: 'always' }], | |
'vue/match-component-file-name': ['error', { extensions: ['vue'], shouldMatchCase: true }], | |
'vue/block-tag-newline': ['error', { maxEmptyLines: 0, multiline: 'always', singleline: 'consistent' }], | |
'vue/component-name-in-template-casing': ['error', 'PascalCase', { registeredComponentsOnly: false }], | |
'vue/define-macros-order': ['error', { | |
order: [ | |
'defineOptions', | |
'defineModel', | |
'defineProps', | |
'defineSlots', | |
'defineEmits', | |
], | |
}], | |
'vue/require-macro-variable-name': ['error', { | |
useSlots: 'slots', | |
useAttrs: 'attrs', | |
defineSlots: 'slots', | |
defineProps: 'props', | |
defineEmits: 'emits', | |
}], | |
}; | |
const stylisticRules = { | |
...stylisticESLint.configs['recommended-flat'].rules, | |
'style/semi-style': 'error', | |
'style/no-extra-semi': 'error', | |
'style/spaced-comment': 'off', | |
'style/indent': ['error', 'tab'], | |
'style/arrow-parens': ['error', 'always'], | |
'style/indent-binary-ops': ['error', 'tab'], | |
'style/operator-linebreak': ['error', 'none'], | |
'style/implicit-arrow-linebreak': ['error', 'beside'], | |
'style/no-mixed-spaces-and-tabs': ['error', 'smart-tabs'], | |
'style/brace-style': ['error', '1tbs', { allowSingleLine: true }], | |
'style/semi': ['error', 'always', { omitLastInOneLineBlock: true }], | |
'style/lines-around-comment': ['error', { | |
allowTypeStart: true, | |
allowEnumStart: true, | |
allowClassStart: true, | |
allowBlockStart: true, | |
allowArrayStart: true, | |
allowModuleStart: true, | |
allowObjectStart: true, | |
allowInterfaceStart: true, | |
}], | |
'style/member-delimiter-style': ['error', { | |
multilineDetection: 'brackets', | |
multiline: { delimiter: 'semi', requireLast: true }, | |
singleline: { delimiter: 'semi', requireLast: false }, | |
}], | |
'style/padding-line-between-statements': [ | |
'error', | |
{ | |
blankLine: 'always', | |
prev: '*', | |
next: [ | |
'do', | |
'try', | |
'for', | |
'iife', | |
'with', | |
'class', | |
'block', | |
'while', | |
'throw', | |
'return', | |
'switch', | |
'export', | |
'function', | |
'directive', | |
'block-like', | |
'cjs-export', | |
'multiline-block-like', | |
], | |
}, | |
{ | |
blankLine: 'any', | |
prev: 'export', | |
next: 'export', | |
}, | |
{ | |
blankLine: 'never', | |
prev: 'function-overload', | |
next: 'function', | |
}, | |
], | |
}; | |
const sharedSortOptions = { 'type': 'line-length', 'ignore-case': true }; | |
const generalRules = { | |
'import/order': 'off', | |
'style/no-tabs': 'off', | |
'antfu/if-newline': 'off', | |
'unused-imports/no-unused-vars': 'off', | |
/* Sorting */ | |
'perfectionist/sort-maps': ['error', sharedSortOptions], | |
'perfectionist/sort-exports': ['error', sharedSortOptions], | |
'perfectionist/sort-union-types': ['error', sharedSortOptions], | |
'perfectionist/sort-array-includes': ['error', sharedSortOptions], | |
'perfectionist/sort-intersection-types': ['error', sharedSortOptions], | |
'perfectionist/sort-named-imports': ['error', { ...sharedSortOptions, 'group-kind': 'types-first' }], | |
'perfectionist/sort-named-exports': ['error', { ...sharedSortOptions, 'group-kind': 'types-first' }], | |
'perfectionist/sort-imports': ['error', { | |
...sharedSortOptions, | |
'internal-pattern': ['@/**'], | |
'custom-groups': { value: { 'vue-components': '@/**/*.vue' } }, | |
'groups': [ | |
['side-effect-style', 'side-effect'], | |
['index-type', 'builtin-type', 'external-type', 'internal-type', 'parent-type', 'sibling-type'], | |
'vue-components', | |
['index', 'builtin', 'external', 'internal', 'parent', 'sibling'], | |
['object', 'unknown'], | |
], | |
}], | |
}; | |
const htmlConfig = { | |
files: ['index.html'], | |
plugins: { '@html-eslint': htmlESLint }, | |
languageOptions: { parser: htmlESLintParser }, | |
rules: { | |
/* Best Practices */ | |
'@html-eslint/no-duplicate-id': 'error', | |
'@html-eslint/require-doctype': 'error', | |
'@html-eslint/no-obsolete-tags': 'error', | |
'@html-eslint/no-duplicate-attrs': 'error', | |
'@html-eslint/require-li-container': 'error', | |
'@html-eslint/require-button-type': 'error', | |
'@html-eslint/no-script-style-type': 'error', | |
'@html-eslint/require-meta-charset': 'error', | |
'@html-eslint/require-closing-tags': ['error', { selfClosing: 'always', allowSelfClosingCustom: true }], | |
/* SEO */ | |
'@html-eslint/require-lang': 'error', | |
'@html-eslint/require-title': 'error', | |
'@html-eslint/no-multiple-h1': 'error', | |
'@html-eslint/require-meta-description': 'error', | |
'@html-eslint/require-open-graph-protocol': 'error', | |
/* Accessibility */ | |
'@html-eslint/require-img-alt': 'error', | |
'@html-eslint/no-abstract-roles': 'error', | |
'@html-eslint/no-accesskey-attrs': 'error', | |
'@html-eslint/require-frame-title': 'error', | |
'@html-eslint/no-aria-hidden-body': 'error', | |
'@html-eslint/no-positive-tabindex': 'error', | |
'@html-eslint/no-skip-heading-levels': 'error', | |
'@html-eslint/require-meta-viewport': 'error', | |
/* Style */ | |
'@html-eslint/quotes': 'error', | |
'@html-eslint/lowercase': 'error', | |
'@html-eslint/indent': ['error', 'tab'], | |
'@html-eslint/no-trailing-spaces': 'error', | |
'@html-eslint/id-naming-convention': 'error', | |
'@html-eslint/no-multiple-empty-lines': 'error', | |
'@html-eslint/element-newline': ['error', { skip: ['pre', 'code'] }], | |
'@html-eslint/no-extra-spacing-attrs': ['error', { enforceBeforeSelfClose: true }], | |
}, | |
}; | |
export default antfuConfig( | |
{ | |
files: ['**/*.js', '**/*.ts', 'src/**/*.vue'], | |
vue: true, | |
typescript: { tsconfigPath: 'tsconfig.json' }, | |
rules: { ...jsRules, ...tsRules, ...stylisticRules }, | |
}, | |
{ files: ['src/**/*.vue'], rules: { ...vueRules, 'no-useless-assignment': 'off' } }, | |
htmlConfig, | |
{ rules: generalRules }, | |
{ ignores: ['**/*.json', 'env.d.ts'] }, | |
); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"type": "module", | |
"scripts": { | |
"dev": "vite", | |
"preview": "vite preview", | |
"type-check": "vue-tsc --build --force", | |
"format": "npx prettier '**/*.{html,css,vue,json}' --config prettier.config.js --write --cache", | |
"lint": "eslint --config eslint.config.js --ignore-pattern '.vscode/*.json' --fix", | |
"build": "vite build", | |
"all": "pnpm run type-check && pnpm run format && pnpm run lint && pnpm run build" | |
}, | |
"dependencies": { | |
"vue": "^3.4.21", | |
}, | |
"devDependencies": { | |
"@antfu/eslint-config": "^2.16.0", | |
"@eslint/js": "^9.1.1", | |
"@html-eslint/eslint-plugin": "^0.24.1", | |
"@html-eslint/parser": "^0.24.1", | |
"@stylistic/eslint-plugin": "^1.7.2", | |
"@tailwindcss/vite": "^4.0.0-alpha.14", | |
"@types/node": "^20.12.7", | |
"@vitejs/plugin-vue": "^5.0.4", | |
"eslint": "^9.2.0", | |
"eslint-plugin-vue": "^9.25.0", | |
"prettier": "^3.2.5", | |
"prettier-plugin-tailwindcss": "^0.5.14", | |
"tailwindcss": "^4.0.0-alpha.14", | |
"typescript": "^5.4.3", | |
"unimport": "^3.7.1", | |
"vite": "^5.2.7", | |
"vite-plugin-vue-devtools": "^7.0.25", | |
"vue-tsc": "^2.0.7" | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export default { | |
useTabs: true, | |
printWidth: 120, | |
singleQuote: true, | |
trailingComma: 'all', | |
quoteProps: 'consistent', | |
plugins: ['prettier-plugin-tailwindcss'], | |
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"editor.formatOnSave": false, | |
"editor.codeActionsOnSave": { | |
"source.fixAll.eslint": "explicit", | |
"source.organizeImports": "never" // Prevent conflicts with “perfectionist/sort-imports” rule. | |
}, | |
/* Silent stylistic rules in the editor, but still auto fix them. */ | |
"eslint.rules.customizations": [ | |
{ "rule": "*sort*", "severity": "off" }, | |
{ "rule": "style/*", "severity": "off" }, | |
{ "rule": "*indent", "severity": "off" }, | |
{ "rule": "*quotes", "severity": "off" }, | |
{ "rule": "*console", "severity": "off" }, | |
{ "rule": "import-*", "severity": "off" }, | |
{ "rule": "*-spaces", "severity": "off" }, | |
{ "rule": "*-spacing", "severity": "off" }, | |
{ "rule": "*newline*", "severity": "off" }, | |
{ "rule": "*attribute*", "severity": "off" }, | |
{ "rule": "format/prettier", "severity": "off" }, | |
{ "rule": "vue/define-macros-order", "severity": "off" }, | |
{ "rule": "*html-closing-bracket-spacing", "severity": "off" } | |
], | |
"eslint.validate": [ | |
"javascript", | |
"javascriptreact", | |
"typescript", | |
"typescriptreact", | |
"vue", | |
"html", | |
"markdown", | |
"json", | |
"jsonc", | |
"yaml", | |
"toml", | |
"gql", | |
"graphql" | |
] | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment